diff --git a/.eslintignore b/.eslintignore index b8b3517f..e99ca799 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,7 +1,7 @@ bundles dist +dist-test docs libs node_modules scripts -**/*.spec.ts diff --git a/.eslintrc.js b/.eslintrc.js index 0b4de84c..b29eff64 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,8 @@ module.exports = { extends: [ + 'airbnb-typescript/base', 'plugin:@typescript-eslint/recommended', + 'plugin:chai-friendly/recommended', ], env: { browser: true, @@ -10,14 +12,20 @@ module.exports = { }, plugins: [ '@typescript-eslint', + 'chai-friendly', ], rules: { // note you must disable the base rule as it can report incorrect errors - 'indent': 'off', '@typescript-eslint/indent': ['error', 2], '@typescript-eslint/explicit-function-return-type': ['off'], '@typescript-eslint/no-explicit-any': ['off'], - '@typescript-eslint/camelcase': ['off'], - '@typescript-eslint/no-empty-interface': ['off'], - }, + '@typescript-eslint/no-unused-expressions': ['off'], + 'chai-friendly/no-unused-expressions': ['error'], + // rule additions for airbnb + 'class-methods-use-this': ['off'], // *may* require further adjustments to instance usage + // exclude cycle dependencies + 'import/no-cycle': ['off'], + 'no-await-in-loop': ['off'], + 'no-restricted-syntax': ['off'] + }, }; diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..c24e25b1 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,25 @@ +name: Lint + +on: pull_request + +jobs: + eslint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + with: + fetch-depth: 1 + - uses: actions/setup-node@v1 + with: + node-version: 12 + - run: rm -f .yarnclean + - run: yarn --frozen-lockfile --ignore-engines --ignore-optional --no-bin-links --non-interactive --silent --ignore-scripts --production=false + env: + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true + HUSKY_SKIP_INSTALL: true + # Alternative: if you use npm instead of yarn + # - run: npm ci --no-audit --prefer-offline + - uses: S3bb1/action-eslint@releases/v1 + with: + repo-token: ${{secrets.GITHUB_TOKEN}} + check-name: eslint # this is the check name from above 👆 where to post annotations \ No newline at end of file diff --git a/.gitignore b/.gitignore index 15be9535..b695fd2a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,12 @@ bundles/* discify/* dist/* +dist-test/* docs/_build/* docs/blockchain/account-store.rst docs/blockchain/event-hub.rst docs/blockchain/executor.rst -docs/blockchain/signer.rst +docs/blockchain/signer-internal.rst docs/common/logger.rst docs/common/validator.rst docs/contracts/contract-loader.rst @@ -18,3 +19,4 @@ package-lock.json package-lock.json.* yarn.lock .DS_Store +yarn-error.log diff --git a/.npmignore b/.npmignore index 7e830b38..0c6e795b 100644 --- a/.npmignore +++ b/.npmignore @@ -1,4 +1,5 @@ dist/index.js.js.map +dist-test/* bundles/bcc/dbcpPath.json discify/ docs/* diff --git a/.travis.yml b/.travis.yml index 56473648..ecd8d6b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,20 +4,26 @@ sudo: required matrix: include: - - name: "services contracts & digital-twin tests" + - name: "service contracts" node_js: "11" env: - - ACCOUNT_MAP='{"0x001De828935e8c7e4cb56Fe610495cAe63fb2612":"01734663843202e2245e5796cb120510506343c67915eb4f9348ac0d8c2cf22a","0x0030C5e7394585400B1FB193DdbCb45a37Ab916E":"7d09c0873e3f8dc0c7282bb7c2ba76bfd432bff53c38ace06193d1e4faa977e7","0x00D1267B27C3A80080f9E1B6Ba01DE313b53Ab58":"a76a2b068fb715830d042ca40b1a4dab8d088b217d11af91d15b972a7afaf202"}' - - TESTSPECS='src/verifications/*.spec.ts src/profile/*.spec.ts src/*.spec.ts src/votings/*.spec.ts src/contracts/digital-twin/*.spec.ts' + - TESTSPECS='src/verifications/*.spec.ts src/profile/*.spec.ts src/*.spec.ts src/votings/*.spec.ts' - CHAIN_ENDPOINT='ws://localhost:7545' script: - - npm run testunitcoverage -- --exclude src/name-resolver.spec.ts + - npm run testunitcoverage + + - name: "digital-twin tests" + node_js: "11" + env: + - TESTSPECS='src/contracts/digital-twin/*.spec.ts' + - CHAIN_ENDPOINT='ws://localhost:7545' + script: + - npm run testunitcoverage - - name: "data contract & IPFS tests" + - name: "data contract & IPFS tests & did/vc tests" node_js: "11" env: - - ACCOUNT_MAP='{"0x001De828935e8c7e4cb56Fe610495cAe63fb2612":"01734663843202e2245e5796cb120510506343c67915eb4f9348ac0d8c2cf22a","0x0030C5e7394585400B1FB193DdbCb45a37Ab916E":"7d09c0873e3f8dc0c7282bb7c2ba76bfd432bff53c38ace06193d1e4faa977e7","0x00D1267B27C3A80080f9E1B6Ba01DE313b53Ab58":"a76a2b068fb715830d042ca40b1a4dab8d088b217d11af91d15b972a7afaf202"}' - - TESTSPECS="src/contracts/data-contract/*.spec.ts src/encryption/*.spec.ts src/dfs/*.spec.ts" + - TESTSPECS="src/contracts/data-contract/*.spec.ts src/encryption/*.spec.ts src/dfs/*.spec.ts src/vc/*.spec.ts src/did/*.spec.ts" - CHAIN_ENDPOINT='ws://localhost:7545' script: - npm run testunitcoverage @@ -25,19 +31,16 @@ matrix: - name: "service/base/businesscenter contract tests" node_js: "11" env: - - ACCOUNT_MAP='{"0x001De828935e8c7e4cb56Fe610495cAe63fb2612":"01734663843202e2245e5796cb120510506343c67915eb4f9348ac0d8c2cf22a","0x0030C5e7394585400B1FB193DdbCb45a37Ab916E":"7d09c0873e3f8dc0c7282bb7c2ba76bfd432bff53c38ace06193d1e4faa977e7","0x00D1267B27C3A80080f9E1B6Ba01DE313b53Ab58":"a76a2b068fb715830d042ca40b1a4dab8d088b217d11af91d15b972a7afaf202"}' - TESTSPECS='src/contracts/*.spec.ts src/contracts/base-contract/*.spec.ts src/contracts/business-center/*.spec.ts src/contracts/service-contract/*.spec.ts' - CHAIN_ENDPOINT='ws://localhost:7545' script: - npm run testunitcoverage -services: - - docker - cache: npm: false - directories: - - docker-cache + +services: + - docker addons: apt: @@ -47,26 +50,20 @@ addons: - gcc-5 - g++-5 - before_install: - - | - filename=docker-cache/saved_images.tar - if [[ -f "$filename" ]]; then docker load < "$filename"; fi - mkdir -p docker-cache - docker pull evannetwork/testcore-snapshot - docker save -o "$filename" evannetwork/testcore-snapshot + - docker pull evannetwork/testcore-snapshot - git clone https://github.com/evannetwork/testcore-config.git && cd testcore-config - docker run -d -p 8546:8546 -p 8545:8545 -v /home/travis/build/evannetwork/api-blockchain-core/testcore-config/testcore.json:/root/parity/spec.json -u root evannetwork/testcore-snapshot --chain /root/parity/spec.json --jsonrpc-interface all --unsafe-expose - export CC="gcc-5" && export CXX="g++-5" && export LINK="gcc-5" && export LINKXX="g++-5" - npm install -g npm@latest - cd .. - - git clone https://github.com/trufflesuite/ganache-cli.git && cd ganache-cli && npm install && cd node_modules && rm -rf ganache-core && git clone https://github.com/trufflesuite/ganache-core.git && cd ganache-core && npm i && cd /home/travis/build/evannetwork/api-blockchain-core + - npm i -g ganache-cli install: - cd /home/travis/build/evannetwork/api-blockchain-core - npm install - rm -rf node_modules/@evan.network/dbcp && git clone https://github.com/evannetwork/dbcp.git ../dbcp && cd ../dbcp && git checkout develop && npm i && npm run build && cd ../api-blockchain-core && mv ../dbcp ./node_modules/@evan.network - cd node_modules/@evan.network && rm -rf smart-contracts-core && git clone https://github.com/evannetwork/smart-contracts-core.git && cd smart-contracts-core && git checkout develop && npm i && node ./scripts/build-contracts.js - - cd ../../../ - - cd ganache-cli && node cli.js --allowUnlimitedContractSize --gasLimit 0xE4E1C0 -p 7545 -f http://localhost:8545 > /dev/null & + - ganache-cli --allowUnlimitedContractSize --gasLimit 0xE4E1C0 -p 7545 -f http://localhost:8545 > /dev/null & - cd /home/travis/build/evannetwork/api-blockchain-core - +after_success: + - bash <(curl -s https://codecov.io/bash) -cF javascript diff --git a/README.md b/README.md index fcf83169..ad5c07c6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The blockchain core is a helper library, that offers helpers for interacting wit ## DApp library -This project is bundled using browserify and directly loadable from dapps within the evan.network. The dbcp.json can be found in this [wrapping project](https://github.com/evannetwork/ui-core/tree/master/dapps/bcc). +This project is bundled using browserify and directly loadable from dapps within the evan.network. The dbcp.json can be found in this [wrapping project](https://github.com/evannetwork/ui-dapps/tree/master/evan-libs/bcc). It's also available as browserified project within the npm, published with the same original versions: `@evan.network/api-blockchain-core-browserified`. diff --git a/VERSIONS.md b/VERSIONS.md index 04e556a2..3e0e492b 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -8,6 +8,22 @@ ### Deprecations +## Version 2.16.0 +### Features +- add `getPublicKey` implementation to `SignerIdentity` +- add `DidResolver` module for managing DID documents +- add possiblity to instantiate a new `SignerIdentity` in two steps + - can be used to create circular structures if required + - first call constructor, you can omit `config` argument + - when rest (e.g. verifications) has been set up you can call `updateConfig` to finalize `SignerIdenty` instantiation +- add `getService`, `setService` to `DidResolver` + +### Fixes +- fix `Container` documentation links and add warning to `ContainerUnshareConfig` documentation +- add interfaces used in exported classes to export list +- update pre-commit hook to use eslint for typescript + + ## Version 2.15.0 ### Features - add `setContainerShareConfigs` to `Container` API @@ -17,6 +33,7 @@ - implement `unPinFileHash` function in dfs - remove old sharing ipfs hash within `saveSharingsToContract` - add `signer-identity` for making transactions via identity contract +- add tests for encryption/decryption with identity based profiles ### Fixes - move `expirationDate` in `formatToV2` to details object @@ -244,7 +261,7 @@ ### Deprecations - remove build scripts for browserify bundle -- remove `bcc/bundles/bcc.ts` file and switch to generalized `index.ts` for both, node and ui bundle (ui build job was moved to [ui-core/dapps/bcc](https://github.com/evannetwork/ui-core/tree/master/dapps/bcc)) +- remove `bcc/bundles/bcc.ts` file and switch to generalized `index.ts` for both, node and ui bundle (ui build job was moved to [ui-dapps/evan-libs/bcc](https://github.com/evannetwork/ui-dapps/tree/master/evan-libs/bcc)) - add dependency to `RightsAndRoles` to `Profile` diff --git a/docs/Makefile b/docs/Makefile index 1e65b152..0bd5322e 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -23,7 +23,7 @@ help: wget -P blockchain -N "$(dbcpDocsUrl)blockchain/account-store.rst" wget -P blockchain -N "$(dbcpDocsUrl)blockchain/event-hub.rst" wget -P blockchain -N "$(dbcpDocsUrl)blockchain/executor.rst" - wget -P blockchain -N "$(dbcpDocsUrl)blockchain/signer.rst" + wget -P blockchain -N "$(dbcpDocsUrl)blockchain/signer-internal.rst" wget -P common -N "$(dbcpDocsUrl)common/logger.rst" wget -P common -N "$(dbcpDocsUrl)common/validator.rst" wget -P contracts -N "$(dbcpDocsUrl)contracts/contract-loader.rst" diff --git a/docs/blockchain/executor-agent.rst b/docs/blockchain/executor-agent.rst index 2e62ad25..6fcd075e 100644 --- a/docs/blockchain/executor-agent.rst +++ b/docs/blockchain/executor-agent.rst @@ -470,7 +470,7 @@ When wanting to perform a contract creation without a factory contract, this con .. _source logLogInterface: ../common/logger.html#logloginterface .. |source signerInterface| replace:: ``SignerInterface`` -.. _source signerInterface: ../blockchain/signer.html +.. _source signerInterface: ../blockchain/signer-internal.html .. |source web3| replace:: ``Web3`` .. _source web3: https://github.com/ethereum/web3.js/ diff --git a/docs/blockchain/executor-wallet.rst b/docs/blockchain/executor-wallet.rst index 2bfbbbb2..d033cdca 100644 --- a/docs/blockchain/executor-wallet.rst +++ b/docs/blockchain/executor-wallet.rst @@ -286,7 +286,7 @@ createContract .. required for building markup .. |source signerInterface| replace:: ``SignerInterface`` -.. _source signerInterface: ../blockchain/signer.html +.. _source signerInterface: ../blockchain/signer-internal.html .. |source eventHub| replace:: ``EventHub`` .. _source eventHub: ../blockchain/event-hub.html diff --git a/docs/blockchain/index.rst b/docs/blockchain/index.rst index 311bbe27..62265c49 100644 --- a/docs/blockchain/index.rst +++ b/docs/blockchain/index.rst @@ -9,7 +9,8 @@ Blockchain executor executor-agent executor-wallet - signer + signer-internal + signer-identity account-store event-hub name-resolver diff --git a/docs/blockchain/signer-identity.rst b/docs/blockchain/signer-identity.rst new file mode 100644 index 00000000..f71478ef --- /dev/null +++ b/docs/blockchain/signer-identity.rst @@ -0,0 +1,313 @@ +================================================================================ +Signer Identity +================================================================================ + +.. list-table:: + :widths: auto + :stub-columns: 1 + + * - Class Name + - SignerIdentity + * - Implements + - `SignerInterface `_ + * - Extends + - `Logger <../common/logger.html>`_ + * - Source + - `signer-idenitity.ts `_ + +The signers are used to create contract transactions and are used internally by the `Executor <../blockchain/executor.html>`_. The default runtime uses the `SignerInternal `_ helper to sign transaction. + +In most cases, you won't have to use the Signer objects directly yourself, as the `Executor <../blockchain/executor.html>`_ is your entry point for performing contract transactions. `SignerIdentity` may be an exception to this rule, as it can be used to check currently used identity and account. + +Note, that this signer supports using accounts and identities. If the `.from` property in the options is given as configured `activeIdentity`, transaction will be made via this identity, which requires the `underlyingAccount` to be in control of this identity. If `.from` is given as `underlyingAcccount`, transactions will be made directly from this account. Also keep in mind, that in both cases `underlyingAccount` needs to have enough funds to pay for the transactions, as this account is used to pay for them. + + + +------------------------------------------------------------------------------ + +.. _signerIdentity_publicProperties: + +Public Properties +================================================================================ + +#. ``activeIdentity`` - ``string``: identity used for transactions, usually controlled by `underlyingAccount` +#. ``underlyingAccount`` - ``string``: account, that pays for transactions used for transactions, usually controlling `activeIdentity` + + + +------------------------------------------------------------------------------ + +.. _signerIdentity_constructor: + +constructor +================================================================================ + +.. code-block:: typescript + + new SignerIdentity(options, config); + +Creates a new `SignerInternal` instance. `config` can be set up later on with `updateConfig`, if required (e.g. when initializing a circular structure). + +---------- +Parameters +---------- + +#. ``options`` - ``SignerIdentityOptions``: options for SignerIdentity constructor (runtime like object) + * ``contractLoader`` - |source contractLoader|_: |source contractLoader|_ instance + * ``verifications`` - |source verifications|_: |source verifications|_ instance + * ``web3`` - |source web3|_: |source web3|_ instance + * ``log`` - ``Function`` (optional): function to use for logging: ``(message, level) => {...}`` + * ``logLevel`` - |source logLevel|_ (optional): messages with this level will be logged with ``log`` + * ``logLog`` - |source logLogInterface|_ (optional): container for collecting log messages + * ``logLogLevel`` - |source logLevel|_ (optional): messages with this level will be pushed to ``logLog`` +#. ``config`` - ``SignerIdentityConfig`` (optional): custom config for `SignerIdentity` instance + * ``activeIdentity`` - ``string``: identity used for transactions, usually controlled by `underlyingAccount` + * ``underlyingAccount`` - ``string``: account, that pays for transactions used for transactions, usually controlled by `underlyingAccount` + * ``underlyingSigner`` - |source signerInterface|_: an instance of a |source signerInterface|_ implementation; usually a |source signerInternal|_ instance + +------- +Returns +------- + +``SignerIdentity`` instance + +------- +Example +------- + +.. code-block:: typescript + + const signer = new SignerIdentity( + { + contractLoader, + verifications, + web3, + }, + { + activeIdentity: await verifications.getIdentityForAccount(accounts[0], true), + underlyingAccount: accounts[0], + underlyingSigner, + }, + ); + + + +------------------------------------------------------------------------------ + +.. _signerIdentity_signAndExecuteSend: + +signAndExecuteSend +=================== + +.. code-block:: javascript + + signer.signAndExecuteSend(options, handleTxResult); + +Performs a value transfer transaction. This will send specified funds to identity, which will send it to target. Funds are returned if transaction fails. + +---------- +Parameters +---------- + +#. ``options`` - ``any``: + * ``from`` - ``string``: The address the call "transaction" should be made from. + * ``to`` - ``string``: The address where the eve's should be send to. + * ``value`` - ``number``: Amount to send in Wei +#. ``handleTxResult`` - ``function(error, receipt)``: callback when transaction receipt is available or error + +------- +Example +------- + +.. code-block:: javascript + + const patchedInput = runtime.signer.signAndExecuteSend({ + from: '0x...', // send from this identity/account + to: '0x...', // receiving account + value: web3.utils.toWei('1'), // amount to send in Wei + }, (err, receipt) => { + console.dir(arguments); + }); + + + +------------------------------------------------------------------------------ + +.. _signerIdentity_signAndExecuteTransaction: + +signAndExecuteTransaction +========================= + +.. code-block:: javascript + + signer.signAndExecuteTransaction(contract, functionName, functionArguments, options, handleTxResult); + +Create, sign and submit a contract transaction. + +---------- +Parameters +---------- + +#. ``contract`` - ``any``: contract instance from api.eth.loadContract(...) +#. ``functionName`` - ``string``: function name +#. ``functionArguments`` - ``any[]``: arguments for contract creation, pass empty Array if no arguments +#. ``options`` - ``any``: + * ``from`` - ``string``: The address (identity/account) the call "transaction" should be made from. + * ``gas`` - ``number``: Amount of gas to attach to the transaction + * ``to`` - ``string`` (optional): The address where the eve's should be send to. + * ``value`` - ``number`` (optional): Amount to send in Wei +#. ``handleTxResult`` - ``function(error, receipt)``: callback when transaction receipt is available or error + + + +------------------------------------------------------------------------------ + +.. _signerIdentity_createContract: + +createContract +=================== + +.. code-block:: javascript + + signer.createContract(contractName, functionArguments, options); + +Creates a smart contract. + +---------- +Parameters +---------- + +#. ``contractName`` - ``any``: contractName from contractLoader +#. ``functionArguments`` - ``any[]``: arguments for contract creation, pass empty Array if no arguments +#. ``options`` - ``any``: + * ``from`` - ``string``: The address the call "transaction" should be made from. + * ``gas`` - ``number``: Amount of gas to attach to the transaction + +------- +Returns +------- + +``Promise`` resolves to ``any``: web3 instance of new contract. + + + +------------------------------------------------------------------------------ + +.. _signerIdentity_signMessage: + +signMessage +=================== + +.. code-block:: javascript + + signer.signMessage(accountId, message); + +Sign given message with accounts private key, does not work for identity. + +---------- +Parameters +---------- + +#. ``accountId`` - ``string``: accountId to sign with, **cannot be done with activeIdentity** +#. ``message`` - ``string``: message to sign + +------- +Returns +------- + +``Promise`` resolves to ``string``: signature + +------- +Example +------- + +.. code-block:: javascript + + const signature = await signer.signMessage(accountId, messageToSign); + + + +-------------------------------------------------------------------------------- + +.. _signerIdentity_updateConfig: + +updateConfig +================================================================================ + +.. code-block:: typescript + + signer.updateConfig(partialOptions, config); + +Update config of `SignerIdentity` can also be used to setup verifications and accounts after initial setup and linking with other modules. + +---------- +Parameters +---------- + +#. ``partialOptions`` - ``{ verifications: Verifications }``: object with `verifications` property, e.g. a runtime +#. ``config`` - ``SignerIdentityConfig``: custom config for `SignerIdentity` instance + * ``activeIdentity`` - ``string``: identity used for transactions, usually controlled by `underlyingAccount` + * ``underlyingAccount`` - ``string``: account, that pays for transactions used for transactions, usually controlled by `underlyingAccount` + * ``underlyingSigner`` - |source signerInterface|_: an instance of a |source signerInterface|_ implementation; usually a |source signerInternal|_ instance + +------- +Returns +------- + +``Promise`` returns ``void``: resolved when done + +------- +Example +------- + +.. code-block:: typescript + + // create new instance + const signer = new SignerIdentity( + { + contractLoader, + verifications, + web3, + }, + ); + + // use instance, e.g. reference it in other components like `verifications` + // ... + + // now set verfications instance and account in signer + signer.updateConfig( + { verifications }, + { + activeIdentity, + underlyingAccount, + underlyingSigner: signerInternal, + }, + ); + + + +.. required for building markup + +.. |source contractLoader| replace:: ``ContractLoader`` +.. _source contractLoader: ../contracts/contract-loader.html + +.. |source keyStoreinterface| replace:: ``KeyStoreInterface`` +.. _source keyStoreinterface: ../blockchain/account-store.html + +.. |source logLevel| replace:: ``LogLevel`` +.. _source logLevel: ../common/logger.html#loglevel + +.. |source logLogInterface| replace:: ``LogLogInterface`` +.. _source logLogInterface: ../common/logger.html#logloginterface + +.. |source signerInterface| replace:: ``SignerInterface`` +.. _source signerInterface: https://github.com/evannetwork/dbcp/tree/master/src/contracts/signer-interface.ts + +.. |source signerInternal| replace:: ``SignerInternal`` +.. _source signerInternal: ../blockchain/signer-internal.html + +.. |source verifications| replace:: ``Verifications`` +.. _source verifications: ../profile/verifications.html + +.. |source web3| replace:: ``Web3`` +.. _source web3: https://github.com/ethereum/web3.js/ diff --git a/docs/contracts/container.rst b/docs/contracts/container.rst index 6051c5aa..bcad845a 100644 --- a/docs/contracts/container.rst +++ b/docs/contracts/container.rst @@ -1146,14 +1146,14 @@ setContainerShareConfigs container.setContainerShareConfigs(newConfigs, originalConfigs); -Takes a full share configuration for a accountId (or a list of them), share newly added properties and unshare removed properties from the container. Also accepts a list / instance of the original sharing configurations duplicated loading can be avoided. +Takes a full share configuration for an accountId (or a list of them), share newly added properties and unshare removed properties from the container. Also accepts a list or instance of the original sharing configurations so that duplicated loading can be avoided. ---------- Parameters ---------- -#. ``newConfigs`` - :ref:`container_ContainerShareConfig` / :ref:`container_ContainerShareConfig`[]: sharing configurations that should be persisted -#. ``unshareConfigs`` - :ref:`container_ContainerShareConfig` / :ref:`container_ContainerShareConfig`[]: pass original share configurations, for that the sharing delta should be built (reduces load time) +#. ``newConfigs`` - :ref:`container_ContainerShareConfig` / :ref:`container_ContainerShareConfig` []: sharing configurations that should be persisted +#. ``originalConfigs`` - :ref:`container_ContainerShareConfig` / :ref:`container_ContainerShareConfig` [] (optional): pass original share configurations to check differences; better performance if provided, automatically fetched if omitted ------- Returns @@ -1535,7 +1535,7 @@ config for unsharing multiple fields from one account (write and/or readWrite ac #. ``readWrite`` - ``string[]`` (optional): list of properties, that are unshared (read and write permissions) #. ``removeListEntries`` - ``string[]`` (optional): list of properties, that are losing the rights to remove listentries #. ``write`` - ``string[]`` (optional): list of properties, for which write permissions should be removed -#. ``force`` - ``boolean`` (optional): Without force flag, removal of the owner will throw an error. By setting to true, force will even remove the owner +#. ``force`` - ``boolean`` (optional): Without force flag, removal of the owner will throw an error. By setting to true, force will even remove the owner. **Important: By removing the owner from a property, the encryptions keys get lost and cannot be recovered. As the result of this, the data isn't readable anymore and must be overwritten by creating new encryption keys to encrypt future content.** .. _container_ContainerPlugin: diff --git a/docs/profile/did.rst b/docs/profile/did.rst new file mode 100644 index 00000000..81b689f5 --- /dev/null +++ b/docs/profile/did.rst @@ -0,0 +1,413 @@ +================================================================================ +DID +================================================================================ + +.. list-table:: + :widths: auto + :stub-columns: 1 + + * - Class Name + - Did + * - Extends + - `Logger <../common/logger.html>`_ + * - Source + - `did.ts `_ + * - Tests + - `did.spec.ts `_ + +The `Did` module allows to interact with DIDs on evan.network. As development of identity and DID handling on evan.network is an ongoing process, this document describes the current interoperability of DIDs on evan.network and can be seen as a work-in-progress state of the current implementation. + + + +-------------------------------------------------------------------------------- + +.. _did_constructor: + +constructor +================================================================================ + +.. code-block:: typescript + + new Did(options); + +Creates a new `Did` instance. + +---------- +Parameters +---------- + +#. ``options`` - ``DidOptions``: options for Did constructor. + * ``contractLoader`` - |source contractLoader|_: |source contractLoader|_ instance + * ``dfs`` - |source dfsInterface|_: |source dfsInterface|_ instance + * ``executor`` - |source executor|_: |source executor|_ instance + * ``nameResolver`` - |source nameResolver|_: |source nameResolver|_ instance + * ``signerIdentity`` - |source signerIdentity|_: |source signerIdentity|_ instance + * ``web3`` - |source web3|_: |source web3|_ instance + * ``log`` - ``Function`` (optional): function to use for logging: ``(message, level) => {...}`` + * ``logLevel`` - |source logLevel|_ (optional): messages with this level will be logged with ``log`` + * ``logLog`` - |source logLogInterface|_ (optional): container for collecting log messages + * ``logLogLevel`` - |source logLevel|_ (optional): messages with this level will be pushed to ``logLog`` + +------- +Returns +------- + +``Did`` instance + +------- +Example +------- + +.. code-block:: typescript + + const did = new Did({ + contractLoader, + dfs, + executor, + nameResolver, + signerIdentity, + web3, + }); + + + +-------------------------------------------------------------------------------- + += Working with DID documents = +============================== + +.. _did_setDidDocument: + +setDidDocument +================================================================================ + +.. code-block:: typescript + + did.setDidDocument(did, document); + +Store given DID document for given DID. + +---------- +Parameters +---------- + +#. ``did`` - ``string``: DID to store DID document for +#. ``document`` - ``any``: DID document to store, ``getDidDocumentTemplate`` can be used as a starting point for DID documents + +------- +Returns +------- + +``Promise`` returns ``void``: resolved when done + +------- +Example +------- + +.. code-block:: typescript + + const identity = await runtime.verifications.getIdentityForAccount(accountsId, true); + const did = await runtime.did.convertIdentityToDid(identity); + const document = await runtime.did.getDidDocumentTemplate(); + await runtime.did.setDidDocument(did, document); + + + +-------------------------------------------------------------------------------- + +.. _did_getDidDocument: + +getDidDocument +================================================================================ + +.. code-block:: typescript + + did.getDidDocument([did]); + +Get DID document for given DID. + +---------- +Parameters +---------- + +#. ``did`` - ``string``: DID to fetch DID document for. + +------- +Returns +------- + +``Promise`` returns ``any``: a DID document that MAY resemble `DidDocumentTemplate` format + +------- +Example +------- + +.. code-block:: typescript + + const identity = await runtime.verifications.getIdentityForAccount(accountsId, true); + const did = await runtime.did.convertIdentityToDid(identity); + const document = await runtime.did.getDidDocumentTemplate(); + await runtime.did.setDidDocument(did, document); + const retrieved = await runtime.did.getDidDocument(did); + + + +-------------------------------------------------------------------------------- + +.. _did_setService: + +setService +================================================================================ + +.. code-block:: typescript + + did.setService(service[, did]); + +Sets service in DID document. + +---------- +Parameters +---------- + +#. ``did`` - ``string``: DID name to set service for +#. ``service`` - ``DidServiceEntry[] | DidServiceEntry``: service to set + +------- +Returns +------- + +``Promise`` returns ``void``: resolved when done + +------- +Example +------- + +.. code-block:: typescript + + const document = await runtime.did.getDidDocumentTemplate(); + const identity = await runtime.verifications.getIdentityForAccount(account, true); + const did = await runtime.did.convertIdentityToDid(identity); + await runtime.did.setDidDocument(did, document); + const service = [{ + id: `${did}#randomService`, + type: `randomService-${random}`, + serviceEndpoint: `https://openid.example.com/${random}`, + }]; + await runtime.did.setService(did, service); + + + +-------------------------------------------------------------------------------- + +.. _did_getService: + +getService +================================================================================ + +.. code-block:: typescript + + did.getService([did]); + +Get service from DID document. + +---------- +Parameters +---------- + +#. ``did`` - ``string``: DID to fetch DID service for. + +------- +Returns +------- + +``Promise`` returns ``DidServiceEntry[] | DidServiceEntry``: service + +------- +Example +------- + +.. code-block:: typescript + + const document = await runtime.did.getDidDocumentTemplate(); + const identity = await runtime.verifications.getIdentityForAccount(account, true); + const did = await runtime.did.convertIdentityToDid(identity); + await runtime.did.setDidDocument(did, document); + const service = [{ + id: `${did}#randomService`, + type: `randomService-${random}`, + serviceEndpoint: `https://openid.example.com/${random}`, + }]; + await runtime.did.setService(did, service); + const retrieved = await runtime.did.getService(did); + + + +-------------------------------------------------------------------------------- + += utilities = +============================== + +.. _did_convertDidToIdentity: + +convertDidToIdentity +================================================================================ + +.. code-block:: typescript + + did.convertDidToIdentity(did); + +Converts given DID to a evan.network identity. + +---------- +Parameters +---------- + +#. ``did`` - ``string``: a DID like "did:evan:testcore:0x000000000000000000000000000000000000001234" + +------- +Returns +------- + +``Promise`` returns ``string``: evan.network identity like "0x000000000000000000000000000000000000001234" + +------- +Example +------- + +.. code-block:: typescript + + const did = 'did:evan:testcore:0x000000000000000000000000000000000000001234'; + const identity = await did.convertDidToIdentity(did); + console.log(identity); + // Output: + // 0x000000000000000000000000000000000000001234 + + + +-------------------------------------------------------------------------------- + +.. _did_convertIdentityToDid: + +convertIdentityToDid +================================================================================ + +.. code-block:: typescript + + did.convertIdentityToDid(identity); + +Converts given evan.network identity hash to DID. + +---------- +Parameters +---------- + +#. ``identity`` - ``string``: evan.network identity like "0x000000000000000000000000000000000000001234" + +------- +Returns +------- + +``Promise`` returns ``string``: a DID like "did:evan:testcore:0x000000000000000000000000000000000000001234" + +------- +Example +------- + +.. code-block:: typescript + + const identity = '0x000000000000000000000000000000000000001234'; + const did = await did.convertIdentityToDid(identity); + console.log(did); + // Output: + // did:evan:testcore:0x000000000000000000000000000000000000001234 + + + +-------------------------------------------------------------------------------- + +.. _did_getDidDocumentTemplate: + +getDidDocumentTemplate +================================================================================ + +.. code-block:: typescript + + did.getDidDocumentTemplate([]); + +Gets a DID document for currently configured account/identity pair. Notice, that this document may a +complete DID document for currently configured active identity, a part of it or not matching it at +all. You can use the result of this function to build a new DID document but should extend it or an +existing DID document, if your details derive from default format. + +All three arguments are optional. When they are used, all of them have to be given and the result +then describes a contracts DID document. If all of them are omitted the result describes an accounts +DID document. + +---------- +Parameters +---------- + +#. ``did`` - ``string`` (optional): contract DID +#. ``controllerDid`` - ``string`` (optional): controller of contracts identity (DID) +#. ``authenticationKey`` - ``string`` (optional): authentication key used for contract + +------- +Returns +------- + +``Promise`` returns ``DidDocumentTemplate``: template for DID document + +------- +Example +------- + +.. code-block:: typescript + + const document = await runtime.did.getDidDocumentTemplate(); + console.log(JSON.stringify(document, null, 2)); + // Output: + // { + // "@context": "https://w3id.org/did/v1", + // "id": "did:evan:testcore:0x126E901F6F408f5E260d95c62E7c73D9B60fd734", + // "publicKey": [ + // { + // "id": "did:evan:testcore:0x126E901F6F408f5E260d95c62E7c73D9B60fd734#key-1", + // "type": [ + // "Secp256k1SignatureVerificationKey2018", + // "ERC725ManagementKey" + // ], + // "publicKeyHex": "045adfd502c0bc55f4fcb90eea36368d7e19c5b3045aa6f51dfa3699046e9751251d21bc6bdd06c1ff0014fcbbf9f1d83c714434f2b33d713aaf46760f2d53f10d" + // } + // ], + // "authentication": [ + // "did:evan:testcore:0x126E901F6F408f5E260d95c62E7c73D9B60fd734#key-1" + // ] + // } + + + +.. required for building markup + +.. |source contractLoader| replace:: ``ContractLoader`` +.. _source contractLoader: ../contracts/contract-loader.html + +.. |source dfsInterface| replace:: ``DfsInterface`` +.. _source dfsInterface: ../dfs/dfs-interface.html + +.. |source executor| replace:: ``Executor`` +.. _source executor: ../blockchain/executor.html + +.. |source logLevel| replace:: ``LogLevel`` +.. _source logLevel: ../common/logger.html#loglevel + +.. |source logLogInterface| replace:: ``LogLogInterface`` +.. _source logLogInterface: ../common/logger.html#logloginterface + +.. |source nameResolver| replace:: ``NameResolver`` +.. _source nameResolver: ../blockchain/name-resolver.html + +.. |source signerIdentity| replace:: ``SignerIdentity`` +.. _source signerIdentity: ../blockchain/signer-identity.html + +.. |source web3| replace:: ``Web3`` +.. _source web3: https://github.com/ethereum/web3.js/ diff --git a/docs/profile/index.rst b/docs/profile/index.rst index 26af8fa7..a2ae8b5e 100644 --- a/docs/profile/index.rst +++ b/docs/profile/index.rst @@ -14,10 +14,12 @@ Profile verifications verification-usage-examples verification-usage-examples-full-output + did + vc Profiles are personal data for accounts. They can be shared with other accounts or used for storing own data. This section contains modules for maintaining profile and interacting with profiles from other accounts. Two types of profiles are supported: - personal `profiles `_, that hold users data like contacts, bookmarks, encryption keys -- `business center profiles `_, that are like contact cards and hold data intended for other participants \ No newline at end of file +- `business center profiles `_, that are like contact cards and hold data intended for other participants diff --git a/docs/profile/mailbox.rst b/docs/profile/mailbox.rst index bb42c064..709e95c3 100644 --- a/docs/profile/mailbox.rst +++ b/docs/profile/mailbox.rst @@ -57,6 +57,7 @@ Parameters * ``keyProvider`` - |source keyProviderInterface|_: |source keyProviderInterface|_ instance * ``mailboxOwner`` - ``string``: account, that will be used, when working with the mailbox * ``nameResolver`` - |source nameResolver|_: |source nameResolver|_ instance + * ``executor`` - |source executor|_ (optional): |source executor|_ instance * ``log`` - ``Function`` (optional): function to use for logging: ``(message, level) => {...}`` * ``logLevel`` - |source logLevel|_ (optional): messages with this level will be logged with ``log`` * ``logLog`` - |source logLogInterface|_ (optional): container for collecting log messages @@ -507,6 +508,9 @@ Example .. |source cryptoProvider| replace:: ``CryptoProvider`` .. _source cryptoProvider: ../encryption/crypto-provider.html +.. |source executor| replace:: ``Executor`` +.. _source executor: ../blockchain/executor.html + .. |source ipfs| replace:: ``Ipfs`` .. _source ipfs: ../dfs/ipfs.html diff --git a/docs/profile/vc.rst b/docs/profile/vc.rst new file mode 100644 index 00000000..c77d89c2 --- /dev/null +++ b/docs/profile/vc.rst @@ -0,0 +1,375 @@ +================================================================================ +VC +================================================================================ + +.. list-table:: + :widths: auto + :stub-columns: 1 + + * - Class Name + - Vc + * - Extends + - `Logger <../common/logger.html>`_ + * - Source + - `vc.ts `_ + * - Tests + - `vc.spec.ts `_ + +The `Vc` module allows to create, store, retrieve and revoke VCs on evan.network. +As development of identities, and DID and VC handling on evan.network is an ongoing process, this document +describes the current interoperability of VCs on evan.network and can be seen as a work-in-progress state +of the current implementation. + + + +-------------------------------------------------------------------------------- + +.. _vc_constructor: + +constructor +================================================================================ + +.. code-block:: typescript + + new Vc(options, did); + +Creates a new `Vc` instance. + +---------- +Parameters +---------- + +#. ``options`` - ``DidOptions``: options for Did constructor. + * ``accountStore`` - |source accountStore|_: |source accountStore|_ instance + * ``activeAccount`` - ``string``: ID of the active account + * ``contractLoader`` - |source contractLoader|_: |source contractLoader|_ instance + * ``dfs`` - |source dfsInterface|_: |source dfsInterface|_ instance + * ``did`` - |source Did|_: |source Did|_ instance for resolving and validating + * ``executor`` - |source executor|_: |source executor|_ instance + * ``nameResolver`` - |source nameResolver|_: |source nameResolver|_ instance + * ``signerIdentity`` - |source signerIdentity|_: |source signerIdentity|_ instance + * ``verifications`` - |source verifications|_: |source verifications|_ instance + * ``web3`` - |source web3|_: |source web3|_ instance + * ``log`` - ``Function`` (optional): function to use for logging: ``(message, level) => {...}`` + * ``logLevel`` - |source logLevel|_ (optional): messages with this level will be logged with ``log`` + * ``logLog`` - |source logLogInterface|_ (optional): container for collecting log messages + * ``logLogLevel`` - |source logLevel|_ (optional): messages with this level will be pushed to ``logLog`` +#. ``credentialStatusEndpoint`` - ``string``: URL of the credential status endpoint + +------- +Returns +------- + +``Vc`` instance + +------- +Example +------- + +.. code-block:: typescript + + const vc = new Vc( + { + accountStore, + activeIdentity + contractLoader, + dfs, + did, + executor, + nameResolver, + signerIdentity, + verifications, + web3, + }, + { credentialStatusEndpoint }, + ); + + + +-------------------------------------------------------------------------------- + += Working with VC documents = +============================== + +.. _vc_createId: + +createId +================================================================================ + +Claim a new ID in the VC registry which can be used later to store a VC **on-chain**. + +.. code-block:: typescript + + vc.createId(); + + +------- +Returns +------- + +``Promise`` returns ``string``: A new ID string + +------- +Example +------- + +.. code-block:: typescript + + const newRegisteredId = await runtime.vc.createId(); + const myVcDocument = { + // Data here, + id: newRegisteredId + }; + await runtime.vc.storeVc(myVcDocument); + +-------------------------------------------------------------------------------- + +.. _vc_createVc: + +createVc +================================================================================ + +Create a signed **off-chain** VC document + +.. code-block:: typescript + + vc.createVc(vcData); + +---------- +Parameters +---------- + +#. ``vcData`` - ``VcDocumentTemplate``: Collection of mandatory and optional VC properties to store in the VC document + +------- +Returns +------- + +``Promise`` returns ``VcDocument``: The final VC document + +------- +Example +------- + +.. code-block:: typescript + + const minimalVcData = { + id: 'randomCustomId', + issuer: { + did: 'someDid', + }, + credentialSubject: { + did: 'someOtherDid', + }, + validFrom: new Date(Date.now()).toISOString() + }; + const offchainVc = await runtime.vc.createVc(minimalVcData); + +-------------------------------------------------------------------------------- + +.. _vc_getVc: + +getVc +================================================================================ + +Get VC document for given VC ID. + +.. code-block:: typescript + + vc.getVc(vcId); + +---------- +Parameters +---------- + +#. ``vcId`` - ``string``: ID to fetch VC document for. Can be either a full VC URI (starting with ``vc:evan:``) or just the VC ID (starting with ``0x``) + +------- +Returns +------- + +``Promise`` returns ``VcDocument``: A VC document + +------- +Example +------- + +.. code-block:: typescript + + const storedVcDoc = await vc.getVc('0x2a838a6961be98f6a182f375bb9158848ee9760ca97a379939ccdf03fc442a23'); + const otherStoredVcDoc = await vc.getVc('vc:evan:testcore:0x2a838a6961be98f6a182f375bb9158848ee9760ca97a379939ccdf03fc442a23'); + +-------------------------------------------------------------------------------- + + +.. _vc_storeVc: + +storeVc +================================================================================ + +.. code-block:: typescript + + vc.storeVc(vcData, shouldRegisterNewId); + +Create a new VC that holds the given data and **store it on the chain**. +Whether a new ID should be registered with the VC registry or the given ID in the document should be used depends of if ``vcData.id`` is set. If set, the method calls ``createId()`` to generate a new ID. + +---------- +Parameters +---------- + +#. ``vcData`` - ``VcDocumentTemplate``: Collection of mandatory and optional VC properties to store in the VC document + +------- +Returns +------- + +``Promise`` returns ``VcDocument``: Returns the VC document as stored on the chain. + +------- +Example +------- + +.. code-block:: typescript + + const minimalVcData = { + issuer: { + did: 'someDid', + }, + credentialSubject: { + did: 'someOtherDid', + }, + validFrom: new Date(Date.now()).toISOString() + }; + const createdVcDoc = await runtime.vc.storeVc(minimalVcData); + const permanentVcAddress = createdVcDoc.id; + +.. code-block:: typescript + const myRegisteredId = await runtime.vc.createId(); + const minimalVcData = { + issuer: { + did: 'someDid', + }, + credentialSubject: { + did: 'someOtherDid' + }, + validFrom: new Date(Date.now()).toISOString() + }; + minimalVcData.id = myRegisteredId; + const createdVcDoc = await runtime.vc.storeVc(minimalVcData); + const permanentVcAddress = createdVcDoc.id; + + + + +-------------------------------------------------------------------------------- + +.. _vc_revokeVc: + +revokeVc +================================================================================ + +.. code-block:: typescript + + vc.revokeVc(vcId); + +Sets a revoke status flag for the VC. + +---------- +Parameters +---------- + +#. ``vcId`` - ``string``: ID for VC document to be revoked. + +------- +Returns +------- + +``Promise`` returns ``void``: resolved when done + +------- +Example +------- + +.. code-block:: typescript + + const storedVcDoc = await vc.getVc(permanentVcAddress); + const vcId = storedVcDoc.id; + + const revokeProcessed = await vc.revokeVc(vcId); + + + +-------------------------------------------------------------------------------- + +.. _vc_getRevokeVcStatus: + +getRevokeVcStatus +================================================================================ + +.. code-block:: typescript + + vc.getRevokeVcStatus(vcId); + +Gets the revoke status flag for the VC. + +---------- +Parameters +---------- + +#. ``vcId`` - ``string``: ID for VC document whose status needs to be retrieved. + +------- +Returns +------- + +``Promise`` returns ``bool``: true for revoked, false for not revoked + +------- +Example +------- + +.. code-block:: typescript + + const storedVcDoc = await vc.getVc(permanentVcAddress); + const vcId = storedVcDoc.id; + + const vcRevokeStatus = await vc.getRevokeVcStatus(vcId); + + + + +.. required for building markup + +.. |source accountStore| replace:: ``AccountStore`` +.. _source accountStore: ../blockchain/account-store.html + +.. |source contractLoader| replace:: ``ContractLoader`` +.. _source contractLoader: ../contracts/contract-loader.html + +.. |source did| replace:: ``Did`` +.. _source did: ./did.html + +.. |source dfsInterface| replace:: ``DfsInterface`` +.. _source dfsInterface: ../dfs/dfs-interface.html + +.. |source executor| replace:: ``Executor`` +.. _source executor: ../blockchain/executor.html + +.. |source logLevel| replace:: ``LogLevel`` +.. _source logLevel: ../common/logger.html#loglevel + +.. |source logLogInterface| replace:: ``LogLogInterface`` +.. _source logLogInterface: ../common/logger.html#logloginterface + +.. |source nameResolver| replace:: ``NameResolver`` +.. _source nameResolver: ../blockchain/name-resolver.html + +.. |source signerIdentity| replace:: ``SignerIdentity`` +.. _source signerIdentity: ../blockchain/signer-identity.html + +.. |source verifications| replace:: ``Verifications`` +.. _source verifications: ./verifications.html + +.. |source web3| replace:: ``Web3`` +.. _source web3: https://github.com/ethereum/web3.js/ diff --git a/package.json b/package.json index 1bfb85e4..3d6e3358 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "author": "evan GmbH", "dependencies": { - "@evan.network/dbcp": "^1.8.4", - "@evan.network/smart-contracts-core": "^2.6.2", + "@evan.network/dbcp": "^1.10.0", + "@evan.network/smart-contracts-core": "^2.8.0", "@types/node": "^12.6.8", "ajv": "^6.10.2", "async-mutex": "^0.1.3", @@ -12,6 +12,7 @@ "bs58": "^4.0.1", "crypto-browserify": "^3.12.0", "crypto-js": "^3.1.9-1", + "did-jwt": "^3.0.0", "elliptic": "^6.4.1", "eth-sig-util": "2.1.0", "ethereumjs-tx": "1.3.4", @@ -34,29 +35,34 @@ "@types/chai": "^4.1.7", "@types/chai-as-promised": "^7.1.0", "@types/mocha": "^5.2.7", - "@typescript-eslint/eslint-plugin": "^1.13.0", - "@typescript-eslint/parser": "^1.13.0", + "@typescript-eslint/eslint-plugin": "^2.11.0", + "@typescript-eslint/parser": "^2.11.0", "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "common-shakeify": "^0.6.2", - "coveralls": "^3.0.2", "disc": "^1.3.3", "env-cmd": "^9.0.3", "eslint": "^6.1.0", + "eslint-config-airbnb": "^18.0.1", + "eslint-config-airbnb-typescript": "^6.3.1", + "eslint-plugin-chai-friendly": "^0.5.0", + "eslint-plugin-import": "^2.19.1", "gulp": "^3.9.1", "gulp-open": "^2.0.0", "gulp-replace": "^0.6.1", "gulp-sourcemaps": "^2.6.1", "husky": "^3.0.2", "mocha": "^6.2.0", - "mocha-lcov-reporter": "^1.3.0", "nyc": "^13.1.0", "ts-node": "^8.3.0", - "tslint": "^5.16.0", - "tslint-no-unused-expression-chai": "^0.1.4", "typescript": "^3.5.3" }, "homepage": "https://evannetwork.github.io/", + "husky": { + "hooks": { + "pre-commit": "eslint --ext .js,.ts -c .eslintrc.js ./src && yarn build-test" + } + }, "keywords": [ "blockchain", "ethereum", @@ -74,6 +80,7 @@ }, "scripts": { "build": "tsc", + "build-test": "tsc -p ./tsconfig-test.json", "build-all": "env-cmd --fallback -f ./.env.local npm run build-contracts && env-cmd --fallback -f ./.env.local npm run build", "build-contracts": "node ./scripts/buildContracts.js", "installl": "npm install && ./scripts/link.sh", @@ -83,8 +90,8 @@ "testunit": "env-cmd --fallback -f ./.env.local npm run build && env-cmd --fallback -f ./.env.local mocha --exit --inspect -r ts-node/register $*", "testunitbail": "env-cmd --fallback -f ./.env.local npm run build && env-cmd --fallback -f ./.env.local mocha --exit -b --inspect -r ts-node/register $*", "testunitbrk": "env-cmd --fallback -f ./.env.local npm run build && env-cmd --fallback -f ./.env.local mocha --exit --inspect-brk -r ts-node/register $*", - "testunitcoverage": "env-cmd --fallback -f ./.env.local npm run build && nyc -r lcov -e .ts -x \"$TESTSPECS\" mocha --exit -r ts-node/register $TESTSPECS && nyc report --reporter=text-lcov | coveralls" + "testunitcoverage": "env-cmd --fallback -f ./.env.local npm run build && nyc -r lcov -e .ts -x \"**/*.spec.ts\" -x \"lib\" mocha --exit -r ts-node/register $TESTSPECS && nyc report --reporter=json > coverage/coverage.json" }, "types": "./dist/index.d.ts", - "version": "2.15.0" + "version": "2.16.0" } diff --git a/scripts/commit-msg.js b/scripts/commit-msg.js index 159b9619..ae533b0d 100644 --- a/scripts/commit-msg.js +++ b/scripts/commit-msg.js @@ -46,7 +46,7 @@ if (trimmed.toLowerCase() === 'merge') { console.log(process.env.EVAN_COMMIT_PATTERN) let messagePattern = process.env.EVAN_COMMIT_PATTERN || // '(?:add|fix|remove|update|refactor|document|merge)\\s.+[\\r\\n]+- \\[CORE-\\d+\\]'; - '(?:add|fix|remove|update|refactor|document|merge)\s.+'; + '(?:add|fix|remove|update|refactor|document|merge)\\s.+'; let messageRegEx = new RegExp(messagePattern, 'i'); // check trimmed commit message with pattern diff --git a/src/common/utils.spec.ts b/src/common/utils.spec.ts index b69f2de8..74ecb4c1 100644 --- a/src/common/utils.spec.ts +++ b/src/common/utils.spec.ts @@ -19,7 +19,7 @@ import 'mocha'; import { expect, use } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); +import * as chaiAsPromised from 'chai-as-promised'; import { accounts } from '../test/accounts'; import { getSmartAgentAuthHeaders } from './utils'; @@ -28,38 +28,37 @@ import { TestUtils } from '../test/test-utils'; use(chaiAsPromised); -function parseAuthData (authData) { +function parseAuthData(authData) { const splitAuthHeader = authData.split(','); const authComponents = {} as any; - splitAuthHeader.forEach(authHeader => { + splitAuthHeader.forEach((authHeader) => { const splitHeader = authHeader.split(' '); - authComponents[splitHeader[0]] = splitHeader[1]; + [, authComponents[splitHeader[0]]] = splitHeader; }); return authComponents; } -function ensureAuth (web3, authData) { +function ensureAuth(web3, authData) { const authComponents = parseAuthData(authData); - const signedTime = parseInt(authComponents.EvanMessage, 10) + const signedTime = parseInt(authComponents.EvanMessage, 10); const maxAge = 1000 * 60 * 5; // max age of signed message is 5m if (signedTime + maxAge < Date.now()) { - throw new Error('signed message has expired') + throw new Error('signed message has expired'); } const authId = web3.eth.accounts.recover( authComponents.EvanMessage, - authComponents.EvanSignedMessage - ) + authComponents.EvanSignedMessage, + ); if (authId !== authComponents.EvanAuth) { - throw new Error('No verified Account.') + throw new Error('No verified Account.'); } - return authComponents + return authComponents; } - -describe('utils', function() { +describe('utils', function test() { this.timeout(60000); let runtime: Runtime; diff --git a/src/common/utils.ts b/src/common/utils.ts index 7d3f968d..2039c927 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -17,12 +17,24 @@ the following URL: https://evan.network/license/ */ -import { Runtime } from '../index' +import { Runtime } from '../index'; export const nullAddress = '0x0000000000000000000000000000000000000000'; export const nullBytes32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; +/** + * retrieves chain name from web3's connected networks id, testcore is 508674158, core is 49262, if + * not matching any of both, chain is threaded as testcore + * + * @param {any} web3 connected web3 instance + * @return {Promise} name of current chain + */ +export async function getEnvironment(web3: any): Promise { + const chainId = await web3.eth.net.getId(); + return chainId === 49262 ? 'core' : 'testcore'; +} + /** * create auth header data to authenticate with current account against a smart agent server * @@ -30,7 +42,9 @@ export const nullBytes32 = '0x00000000000000000000000000000000000000000000000000 * @param {string} message (optional): message to sign, uses current timestamp by default * @return {Promise} auth header value as string */ -export async function getSmartAgentAuthHeaders(runtime: Runtime, message?: string +export async function getSmartAgentAuthHeaders( + runtime: Runtime, + message?: string, ): Promise { const messageToSign = message || `${new Date().getTime()}`; const hexMessage = runtime.web3.utils.toHex(messageToSign); @@ -39,22 +53,10 @@ export async function getSmartAgentAuthHeaders(runtime: Runtime, message?: strin return [ `EvanAuth ${runtime.activeAccount}`, `EvanMessage ${paddedMessage}`, - `EvanSignedMessage ${signature}` + `EvanSignedMessage ${signature}`, ].join(','); } -/** - * retrieves chain name from web3's connected networks id, testcore is 508674158, core is 49262, if - * not matching any of both, chain is threaded as testcore - * - * @param {any} web3 connected web3 instance - * @return {Promise} name of current chain - */ -export async function getEnvironment(web3: any): Promise { - const chainId = await web3.eth.net.getId(); - return chainId === 49262 ? 'core' : 'testcore'; -} - /** * obfuscates strings by replacing each character but the last two with 'x' * @@ -62,11 +64,13 @@ export async function getEnvironment(web3: any): Promise { * @return {string} obfuscated text */ export function obfuscate(text: string): string { - return text ? `${[...Array(text.length - 2)].map(() => 'x').join('')}${text.substr(text.length - 2)}` : text; + return text + ? `${[...Array(text.length - 2)].map(() => 'x').join('')}${text.substr(text.length - 2)}` + : text; } /** -* run given function from this, use function(error, result) {...} callback for promise resolve/reject +* run given function from this, use function(error, result) {..} callback for promise resolve/reject * can be used like: * api.helpers * .runFunctionAsPromise(fs, 'readFile', 'somefile.txt') @@ -75,15 +79,15 @@ export function obfuscate(text: string): string { * * @param {Object} funThis the functions 'this' object * @param {string} functionName name of the contract function to call -* @return {Promise} resolves to: {Object} (the result from the function(error, result) {...} callback) +* @return {Promise} resolves to: {Object} */ export async function promisify(funThis, functionName, ...args): Promise { - let functionArguments = args.slice(0); + const functionArguments = args.slice(0); - return new Promise(function(resolve, reject) { + return new Promise(((resolve, reject) => { try { // add callback function to arguments - functionArguments.push(function(error, result) { + functionArguments.push((error, result) => { if (error) { reject(error); } else { @@ -91,9 +95,9 @@ export async function promisify(funThis, functionName, ...args): Promise { } }); // run function - funThis[functionName].apply(funThis, functionArguments); + funThis[functionName](...functionArguments); } catch (ex) { reject(ex.message); } - }); + })); } diff --git a/src/config-core.ts b/src/config-core.ts index baf02187..1f390f23 100644 --- a/src/config-core.ts +++ b/src/config-core.ts @@ -25,18 +25,21 @@ const configCore = { admin: 'admin', businessCenterRoot: process.env.BC_ROOT || 'testbc.evan', container: 'container', + dids: 'dids', ensRoot: process.env.ENS_ROOT || 'evan', eventhub: 'eventhub', factory: 'factory', index: 'index', mailbox: 'mailbox', profile: 'profile', + vcs: 'vcs', wallet: 'wallet', }, domains: { adminFactory: ['admin', 'factory', 'ensRoot'], businessCenter: ['businessCenterRoot'], containerFactory: ['container', 'factory', 'ensRoot'], + didRegistry: ['dids', 'ensRoot'], eventhub: process.env.ENS_EVENTS || ['eventhub', 'ensRoot'], factory: ['factory', 'businessCenterRoot'], indexFactory: ['index', 'factory', 'ensRoot'], @@ -44,17 +47,22 @@ const configCore = { profile: process.env.ENS_PROFILES || ['profile', 'ensRoot'], profileFactory: ['profile', 'factory', 'ensRoot'], root: ['ensRoot'], + vcRegistry: ['vcs', 'test', 'ensRoot'], }, }, smartAgents: { onboarding: { accountId: '0x063fB42cCe4CA5448D69b4418cb89E663E71A139', }, + didAndVc: { + vcRevokationStatusEndpoint: '', + }, }, alwaysAutoGasLimit: 10, // owner of the evan root verification domain ensRootOwner: '0xBa5384267A175542CB0E98a37875C106decDc3C3', - ipfsConfig: {host: 'ipfs.evan.network', port: '443', protocol: 'https'}, -} + ipfsConfig: { host: 'ipfs.evan.network', port: '443', protocol: 'https' }, +}; -export { configCore } +// eslint-disable-next-line import/prefer-default-export +export { configCore }; diff --git a/src/config-testcore.ts b/src/config-testcore.ts index cc2ff00a..45cb31b5 100644 --- a/src/config-testcore.ts +++ b/src/config-testcore.ts @@ -25,18 +25,21 @@ const configTestcore = { admin: 'admin', businessCenterRoot: process.env.BC_ROOT || 'testbc.evan', container: 'container', + dids: 'dids', ensRoot: process.env.ENS_ROOT || 'evan', eventhub: 'eventhub', factory: 'factory', index: 'index', mailbox: 'mailbox', profile: 'profile', + vcs: 'vcs', wallet: 'wallet', }, domains: { adminFactory: ['admin', 'factory', 'ensRoot'], businessCenter: ['businessCenterRoot'], containerFactory: ['container', 'factory', 'ensRoot'], + didRegistry: ['dids', 'ensRoot'], eventhub: process.env.ENS_EVENTS || ['eventhub', 'ensRoot'], factory: ['factory', 'businessCenterRoot'], indexFactory: ['index', 'factory', 'ensRoot'], @@ -44,17 +47,22 @@ const configTestcore = { profile: process.env.ENS_PROFILES || ['profile', 'ensRoot'], profileFactory: ['profile', 'factory', 'ensRoot'], root: ['ensRoot'], + vcRegistry: ['vcs', 'ensRoot'], }, }, smartAgents: { onboarding: { accountId: '0x063fB42cCe4CA5448D69b4418cb89E663E71A139', }, + didAndVc: { + vcRevokationStatusEndpoint: 'https://testcore.evan.network/smart-agents/smart-agent-did-resolver/vc/status/', + }, }, alwaysAutoGasLimit: 10, // owner of the evan root verification domain ensRootOwner: '0x4a6723fC5a926FA150bAeAf04bfD673B056Ba83D', - ipfsConfig: {host: 'ipfs.test.evan.network', port: '443', protocol: 'https'}, -} + ipfsConfig: { host: 'ipfs.test.evan.network', port: '443', protocol: 'https' }, +}; -export { configTestcore } +// eslint-disable-next-line import/prefer-default-export +export { configTestcore }; diff --git a/src/contracts/base-contract/base-contract.spec.ts b/src/contracts/base-contract/base-contract.spec.ts index 051fd4cf..93d383b9 100644 --- a/src/contracts/base-contract/base-contract.spec.ts +++ b/src/contracts/base-contract/base-contract.spec.ts @@ -18,14 +18,12 @@ */ import 'mocha'; +import * as chaiAsPromised from 'chai-as-promised'; import { expect, use } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); - import { ContractLoader, EventHub, Executor, - NameResolver, } from '@evan.network/dbcp'; import { accounts } from '../../test/accounts'; @@ -36,17 +34,12 @@ import { Ipld } from '../../dfs/ipld'; import { Profile } from '../../profile/profile'; import { TestUtils } from '../../test/test-utils'; -use(chaiAsPromised); - -function timeout(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} +use(chaiAsPromised); -describe('BaseContract', function() { +describe('BaseContract', function test() { this.timeout(60000); let baseContract: BaseContract; - let contractFactory: any; let executor: Executor; let ipfs: Ipfs; let ipld: Ipld; @@ -79,14 +72,14 @@ describe('BaseContract', function() { profile = await TestUtils.getProfile(web3, ipfs); await profile.loadForAccount(); // await profile.setContactKnownState(accounts[0], true); - if (!await executor.executeContractCall(businessCenter, 'isMember', accounts[0], { from: accounts[0], })) { - await executor.executeContractTransaction(businessCenter, 'join', { from: accounts[0], autoGas: 1.1, }); + if (!await executor.executeContractCall(businessCenter, 'isMember', accounts[0], { from: accounts[0] })) { + await executor.executeContractTransaction(businessCenter, 'join', { from: accounts[0], autoGas: 1.1 }); } - if (!await executor.executeContractCall(businessCenter, 'isMember', accounts[1], { from: accounts[1], })) { - await executor.executeContractTransaction(businessCenter, 'join', { from: accounts[1], autoGas: 1.1, }); + if (!await executor.executeContractCall(businessCenter, 'isMember', accounts[1], { from: accounts[1] })) { + await executor.executeContractTransaction(businessCenter, 'join', { from: accounts[1], autoGas: 1.1 }); } - if (!await executor.executeContractCall(businessCenter, 'isMember', accounts[2], { from: accounts[2], })) { - await executor.executeContractTransaction(businessCenter, 'join', { from: accounts[2], autoGas: 1.1, }); + if (!await executor.executeContractCall(businessCenter, 'isMember', accounts[2], { from: accounts[2] })) { + await executor.executeContractTransaction(businessCenter, 'join', { from: accounts[2], autoGas: 1.1 }); } }); @@ -94,7 +87,8 @@ describe('BaseContract', function() { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - businessCenterDomain); + businessCenterDomain, + ); expect(contractId).not.to.be.undefined; }); @@ -102,7 +96,8 @@ describe('BaseContract', function() { const contractPromise = baseContract.createUninitialized( 'testdatacontract', accounts[0], - 'testdatacontract.factory.testbc.evan'); + 'testdatacontract.factory.testbc.evan', + ); expect(contractPromise).to.be.rejected; }); @@ -110,7 +105,8 @@ describe('BaseContract', function() { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - businessCenterDomain); + businessCenterDomain, + ); const contract = loader.loadContract('BaseContractInterface', contractId); let isMember = await executor.executeContractCall(contract, 'isConsumer', accounts[1]); expect(isMember).to.be.false; @@ -128,7 +124,8 @@ describe('BaseContract', function() { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - businessCenterDomain); + businessCenterDomain, + ); const contract = loader.loadContract('BaseContractInterface', contractId); let isMember = await executor.executeContractCall(contract, 'isConsumer', accounts[1]); expect(isMember).to.be.false; @@ -154,8 +151,8 @@ describe('BaseContract', function() { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - businessCenterDomain); - const contract = loader.loadContract('BaseContractInterface', contractId); + businessCenterDomain, + ); await baseContract.inviteToContract( businessCenterDomain, contractId, @@ -175,9 +172,8 @@ describe('BaseContract', function() { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - businessCenterDomain); - const contract = loader.loadContract('BaseContractInterface', contractId); - let isMember = await executor.executeContractCall(contract, 'isConsumer', accounts[1]); + businessCenterDomain, + ); const promise = baseContract.inviteToContract( businessCenterDomain, contractId, @@ -192,9 +188,10 @@ describe('BaseContract', function() { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - businessCenterDomain); + businessCenterDomain, + ); const contract = loader.loadContract('BaseContractInterface', contractId); - let isMember = await executor.executeContractCall(contract, 'isConsumer', accounts[1]); + const isMember = await executor.executeContractCall(contract, 'isConsumer', accounts[1]); expect(isMember).to.be.false; await profile.setContactKnownState(accounts[0], false); const invitePromise = baseContract.inviteToContract( @@ -211,7 +208,8 @@ describe('BaseContract', function() { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - businessCenterDomain); + businessCenterDomain, + ); await baseContract.changeContractState(contractId, accounts[0], ContractState.PendingApproval); }); @@ -219,14 +217,19 @@ describe('BaseContract', function() { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - businessCenterDomain); + businessCenterDomain, + ); await baseContract.inviteToContract( businessCenterDomain, contractId, accounts[0], accounts[1], ); - const promise = baseContract.changeContractState(contractId, accounts[1], ContractState.PendingApproval); + const promise = baseContract.changeContractState( + contractId, + accounts[1], + ContractState.PendingApproval, + ); await expect(promise).to.be.rejected; }); @@ -234,7 +237,8 @@ describe('BaseContract', function() { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - businessCenterDomain); + businessCenterDomain, + ); const promise = baseContract.changeContractState(contractId, accounts[1], 1); await expect(promise).to.be.rejected; }); @@ -243,10 +247,14 @@ describe('BaseContract', function() { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - businessCenterDomain); - const contract = loader.loadContract('BaseContract', contractId) - const cstate = await executor.executeContractCall(contract, 'consumerState', accounts[0]); - await baseContract.changeConsumerState(contractId, accounts[0], accounts[0], ConsumerState.Active); + businessCenterDomain, + ); + await baseContract.changeConsumerState( + contractId, + accounts[0], + accounts[0], + ConsumerState.Active, + ); }); describe('when used without a business center', () => { @@ -254,17 +262,22 @@ describe('BaseContract', function() { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - null); - const contract = loader.loadContract('BaseContract', contractId) - const cstate = await executor.executeContractCall(contract, 'consumerState', accounts[0]); - await baseContract.changeConsumerState(contractId, accounts[0], accounts[0], ConsumerState.Active); + null, + ); + await baseContract.changeConsumerState( + contractId, + accounts[0], + accounts[0], + ConsumerState.Active, + ); }); it('can have new members invited to it by the owner', async () => { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - null); + null, + ); const contract = loader.loadContract('BaseContractInterface', contractId); let isMember = await executor.executeContractCall(contract, 'isConsumer', accounts[1]); expect(isMember).to.be.false; @@ -282,7 +295,8 @@ describe('BaseContract', function() { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - null); + null, + ); const contract = loader.loadContract('BaseContractInterface', contractId); let isMember = await executor.executeContractCall(contract, 'isConsumer', accounts[1]); expect(isMember).to.be.false; @@ -304,19 +318,21 @@ describe('BaseContract', function() { expect(isMember).to.be.false; }); + // eslint-disable-next-line it('triggers contract events from on its own instead of letting the bc do this', async () => { + // eslint-disable-next-line return new Promise(async (resolve, reject) => { try { const contractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], - null); - const contract = loader.loadContract('BaseContractInterface', contractId); + null, + ); // reject on timeout let resolved; setTimeout(() => { if (!resolved) { - reject('timeout during waiting for ContractEvent'); + reject(new Error('timeout during waiting for ContractEvent')); } }, 10000); // if event is triggered, resolve test @@ -325,8 +341,9 @@ describe('BaseContract', function() { const { sender, eventType } = event.returnValues; return sender === contractId && eventType.toString() === '0'; }, - (event) => { resolved = true; resolve(); } - ); + () => { + resolved = true; resolve(); + }); await baseContract.inviteToContract( null, contractId, diff --git a/src/contracts/base-contract/base-contract.ts b/src/contracts/base-contract/base-contract.ts index 938f8ce4..4bd06c02 100644 --- a/src/contracts/base-contract/base-contract.ts +++ b/src/contracts/base-contract/base-contract.ts @@ -22,7 +22,7 @@ import { Executor, Logger, LoggerOptions, - NameResolver + NameResolver, } from '@evan.network/dbcp'; @@ -38,27 +38,27 @@ export enum ContractState { Active, VerifyTerminated, Terminated, -}; +} /** * describes the state of a consumer or owner in a contract */ export enum ConsumerState { - Initial, - Error, - Draft, - Rejected, - Active, - Terminated -}; + Initial, + Error, + Draft, + Rejected, + Active, + Terminated +} /** * options for BaseContract constructor */ export interface BaseContractOptions extends LoggerOptions { - executor: Executor, - loader: ContractLoader, - nameResolver: NameResolver, + executor: Executor; + loader: ContractLoader; + nameResolver: NameResolver; } /** @@ -69,7 +69,7 @@ export interface BaseContractOptions extends LoggerOptions { export class BaseContract extends Logger { protected options: BaseContractOptions; - constructor(optionsInput: BaseContractOptions) { + public constructor(optionsInput: BaseContractOptions) { super(optionsInput); this.options = optionsInput; } @@ -86,11 +86,11 @@ export class BaseContract extends Logger { * @return {Promise} Ethereum id of new contract */ public async createUninitialized( - factoryName: string, - accountId: string, - businessCenterDomain?: string, - descriptionDfsHash = '0x0000000000000000000000000000000000000000000000000000000000000000') - : Promise { + factoryName: string, + accountId: string, + businessCenterDomain?: string, + descriptionDfsHash = '0x0000000000000000000000000000000000000000000000000000000000000000', + ): Promise { let factoryAddress; if (factoryName.startsWith('0x')) { factoryAddress = factoryName; @@ -102,7 +102,8 @@ export class BaseContract extends Logger { } else { // partial name, bc relative domain factoryDomain = this.options.nameResolver.getDomainName( - this.options.nameResolver.config.domains.factory, factoryName); + this.options.nameResolver.config.domains.factory, factoryName, + ); } factoryAddress = await this.options.nameResolver.getAddress(factoryDomain); } @@ -116,13 +117,13 @@ export class BaseContract extends Logger { if (businessCenterAddress !== '0x0000000000000000000000000000000000000000') { const businessCenterContract = await this.options.loader.loadContract( 'BusinessCenterInterface', - businessCenterAddress + businessCenterAddress, ); try { await this.options.executor.executeContractCall( businessCenterContract, - 'joinSchema' + 'joinSchema', ); } catch (e) { throw new Error('There is no Business Center domain exisiting'); @@ -136,7 +137,7 @@ export class BaseContract extends Logger { 'createContract', { from: accountId, autoGas: 1.1, - event: { target: 'BaseContractFactoryInterface', eventName: 'ContractCreated', }, + event: { target: 'BaseContractFactoryInterface', eventName: 'ContractCreated' }, getEventResult: (event, args) => args.newAddress, }, businessCenterAddress, @@ -159,12 +160,13 @@ export class BaseContract extends Logger { * @return {Promise} resolved when done */ public async inviteToContract( - businessCenterDomain: string, - contract: string, - inviterId: string, - inviteeId: string): Promise { - const baseContractInterface = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('BaseContractInterface', contract); + businessCenterDomain: string, + contract: string, + inviterId: string, + inviteeId: string, + ): Promise { + const baseContractInterface = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('BaseContractInterface', contract); let businessCenterAddress; if (businessCenterDomain) { businessCenterAddress = await this.options.nameResolver.getAddress(businessCenterDomain); @@ -174,7 +176,7 @@ export class BaseContract extends Logger { await this.options.executor.executeContractTransaction( baseContractInterface, 'inviteConsumer', - { from: inviterId, autoGas: 1.1, }, + { from: inviterId, autoGas: 1.1 }, inviteeId, businessCenterAddress, ); @@ -188,13 +190,17 @@ export class BaseContract extends Logger { * @param {ContractState} state new state * @return {Promise} resolved when done */ - public async changeContractState(contract: string|any, accountId: string, state: ContractState): Promise { - const baseContractInterface = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('BaseContractInterface', contract); + public async changeContractState( + contract: string|any, + accountId: string, + state: ContractState, + ): Promise { + const baseContractInterface = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('BaseContractInterface', contract); await this.options.executor.executeContractTransaction( baseContractInterface, 'changeContractState', - { from: accountId, autoGas: 1.1, }, + { from: accountId, autoGas: 1.1 }, state, ); } @@ -209,16 +215,17 @@ export class BaseContract extends Logger { * @return {Promise} resolved when done */ public async changeConsumerState( - contract: string|any, - accountId: string, - consumerId: string, - state: ConsumerState): Promise { - const baseContractInterface = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('BaseContractInterface', contract); + contract: string|any, + accountId: string, + consumerId: string, + state: ConsumerState, + ): Promise { + const baseContractInterface = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('BaseContractInterface', contract); await this.options.executor.executeContractTransaction( baseContractInterface, 'changeConsumerState', - { from: accountId, autoGas: 1.1, }, + { from: accountId, autoGas: 1.1 }, consumerId, state, ); @@ -236,12 +243,13 @@ export class BaseContract extends Logger { * @return {Promise} resolved when done */ public async removeFromContract( - businessCenterDomain: string, - contract: string, - accountId: string, - idToBeRemoved: string): Promise { - const baseContractInterface = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('BaseContractInterface', contract); + businessCenterDomain: string, + contract: string, + accountId: string, + idToBeRemoved: string, + ): Promise { + const baseContractInterface = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('BaseContractInterface', contract); let businessCenterAddress; if (businessCenterDomain) { businessCenterAddress = await this.options.nameResolver.getAddress(businessCenterDomain); @@ -251,9 +259,9 @@ export class BaseContract extends Logger { await this.options.executor.executeContractTransaction( baseContractInterface, 'removeConsumer', - { from: accountId, autoGas: 1.1, }, + { from: accountId, autoGas: 1.1 }, idToBeRemoved, - businessCenterAddress + businessCenterAddress, ); } } diff --git a/src/contracts/business-center/business-center.spec.ts b/src/contracts/business-center/business-center.spec.ts index af1a6e9e..d745f119 100644 --- a/src/contracts/business-center/business-center.spec.ts +++ b/src/contracts/business-center/business-center.spec.ts @@ -19,7 +19,7 @@ import 'mocha'; import { expect, use } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); +import * as chaiAsPromised from 'chai-as-promised'; import { Executor, @@ -33,7 +33,7 @@ import { TestUtils } from '../../test/test-utils'; use(chaiAsPromised); -describe('Business Center', function() { +describe('Business Center', function test() { this.timeout(60000); let businessCenter; let executor: Executor; @@ -54,7 +54,9 @@ describe('Business Center', function() { * @return {Promise} contract instance */ async function createBusinessCenter(joinSchema: number): Promise { - const adminFactoryEnsDomain = nameResolver.getDomainName(config.nameResolver.domains.adminFactory); + const adminFactoryEnsDomain = nameResolver.getDomainName( + config.nameResolver.domains.adminFactory, + ); const adminFactoryContractAddress = await nameResolver.getAddress(adminFactoryEnsDomain); const adminFactory = await loader.loadContract('BusinessCenterFactory', adminFactoryContractAddress); const address = await executor.executeContractTransaction( @@ -63,22 +65,22 @@ describe('Business Center', function() { { from: accounts[0], gas: 5000000, - event: { target: 'BusinessCenterFactory', eventName: 'ContractCreated', }, + event: { target: 'BusinessCenterFactory', eventName: 'ContractCreated' }, getEventResult: (event, args) => args.newAddress, }, nameResolver.namehash(ensDomain), - config.nameResolver.ensAddress + config.nameResolver.ensAddress, ); const bc = loader.loadContract('BusinessCenterInterface', address); const storageAddress = '0x0000000000000000000000000000000000000000'; await executor.executeContractTransaction( bc, 'init', - { from: accounts[0], autoGas: 1.1, }, + { from: accounts[0], autoGas: 1.1 }, storageAddress, joinSchema, ); - await executor.executeContractTransaction(bc, 'join', { from: accounts[0], autoGas: 1.1, }); + await executor.executeContractTransaction(bc, 'join', { from: accounts[0], autoGas: 1.1 }); return bc; } @@ -89,11 +91,13 @@ describe('Business Center', function() { nameResolver = await TestUtils.getNameResolver(web3); ensDomain = nameResolver.getDomainName(config.nameResolver.domains.businessCenter); businessCenter = await createBusinessCenter(0); - let isMember = await executor.executeContractCall( - businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + const isMember = await executor.executeContractCall( + businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); if (!isMember) { await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[2], autoGas: 1.1, }); + businessCenter, 'join', { from: accounts[2], autoGas: 1.1 }, + ); } }); @@ -101,16 +105,19 @@ describe('Business Center', function() { it('allows to join', async () => { const selfJoinBc = await createBusinessCenter(0); await executor.executeContractTransaction( - selfJoinBc, 'join', { from: accounts[2], autoGas: 1.1, }); - let isMember = await executor.executeContractCall( - selfJoinBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + selfJoinBc, 'join', { from: accounts[2], autoGas: 1.1 }, + ); + const isMember = await executor.executeContractCall( + selfJoinBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); expect(isMember).to.be.true; }); it('rejects an invite of a member', async () => { const selfJoinBc = await createBusinessCenter(0); const invitePromise = executor.executeContractTransaction( - selfJoinBc, 'invite', { from: accounts[0], autoGas: 1.1, }, accounts[2]); + selfJoinBc, 'invite', { from: accounts[0], autoGas: 1.1 }, accounts[2], + ); expect(invitePromise).to.be.rejected; }); }); @@ -119,15 +126,17 @@ describe('Business Center', function() { it('rejects a join', async () => { const inviteOnlyBc = await createBusinessCenter(1); const joinPromise = executor.executeContractTransaction( - inviteOnlyBc, 'join', { from: accounts[2], autoGas: 1.1, }); + inviteOnlyBc, 'join', { from: accounts[2], autoGas: 1.1 }, + ); expect(joinPromise).to.be.rejected; }); it('adds a member, when a member is invited', async () => { const inviteOnlyBc = await createBusinessCenter(1); await executor.executeContractTransaction( - inviteOnlyBc, 'invite', { from: accounts[0], autoGas: 1.1, }, accounts[2], ); - let isMember = await executor.executeContractCall(inviteOnlyBc, 'isMember', accounts[2]); + inviteOnlyBc, 'invite', { from: accounts[0], autoGas: 1.1 }, accounts[2], + ); + const isMember = await executor.executeContractCall(inviteOnlyBc, 'isMember', accounts[2]); expect(isMember).to.be.true; }); }); @@ -136,27 +145,33 @@ describe('Business Center', function() { it('allows a join request, but does not add a member with only this', async () => { const handshakeBc = await createBusinessCenter(2); await executor.executeContractTransaction( - handshakeBc, 'join', { from: accounts[2], autoGas: 1.1, }); + handshakeBc, 'join', { from: accounts[2], autoGas: 1.1 }, + ); }); it('allows sending invitations, but does not add a member with only this', async () => { const handshakeBc = await createBusinessCenter(2); await executor.executeContractTransaction( - handshakeBc, 'invite', { from: accounts[0], autoGas: 1.1, }, accounts[2]); + handshakeBc, 'invite', { from: accounts[0], autoGas: 1.1 }, accounts[2], + ); }); it('adds a member when invite and join have been called', async () => { let isMember; const handshakeBc = await createBusinessCenter(2); await executor.executeContractTransaction( - handshakeBc, 'join', { from: accounts[2], autoGas: 1.1, }); + handshakeBc, 'join', { from: accounts[2], autoGas: 1.1 }, + ); isMember = await executor.executeContractCall( - handshakeBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + handshakeBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); expect(isMember).to.be.false; await executor.executeContractTransaction( - handshakeBc, 'invite', { from: accounts[0], autoGas: 1.1, }, accounts[2]); + handshakeBc, 'invite', { from: accounts[0], autoGas: 1.1 }, accounts[2], + ); isMember = await executor.executeContractCall( - handshakeBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + handshakeBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); expect(isMember).to.be.true; }); @@ -164,14 +179,18 @@ describe('Business Center', function() { let isMember; const handshakeBc = await createBusinessCenter(2); await executor.executeContractTransaction( - handshakeBc, 'invite', { from: accounts[0], autoGas: 1.1, }, accounts[2]); + handshakeBc, 'invite', { from: accounts[0], autoGas: 1.1 }, accounts[2], + ); isMember = await executor.executeContractCall( - handshakeBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + handshakeBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); expect(isMember).to.be.false; await executor.executeContractTransaction( - handshakeBc, 'join', { from: accounts[2], autoGas: 1.1, }); + handshakeBc, 'join', { from: accounts[2], autoGas: 1.1 }, + ); isMember = await executor.executeContractCall( - handshakeBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + handshakeBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); expect(isMember).to.be.true; }); }); @@ -184,49 +203,58 @@ describe('Business Center', function() { describe('when working with a SelfJoin Business Center', async () => { it('allows to join', async () => { await executor.executeContractTransaction( - currentBc, 'join', { from: accounts[2], autoGas: 1.1, }); - let isMember = await executor.executeContractCall( - currentBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + currentBc, 'join', { from: accounts[2], autoGas: 1.1 }, + ); + const isMember = await executor.executeContractCall( + currentBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); expect(isMember).to.be.true; }); it('rejects an invite of a member', async () => { const invitePromise = executor.executeContractTransaction( - currentBc, 'invite', { from: accounts[0], autoGas: 1.1, }, accounts[1]); + currentBc, 'invite', { from: accounts[0], autoGas: 1.1 }, accounts[1], + ); expect(invitePromise).to.be.rejected; }); it('allows to change the join Schema', async () => { await executor.executeContractTransaction( - currentBc, 'setJoinSchema', { from: accounts[0], autoGas: 1.1, }, 1); + currentBc, 'setJoinSchema', { from: accounts[0], autoGas: 1.1 }, 1, + ); }); it('does not allow to change the join schema', async () => { const setJoinSchemaPromise = executor.executeContractTransaction( - currentBc, 'setJoinSchema', { from: accounts[2], autoGas: 1.1, }, 1); + currentBc, 'setJoinSchema', { from: accounts[2], autoGas: 1.1 }, 1, + ); expect(setJoinSchemaPromise).to.be.rejected; }); it('allows to leave the business center', async () => { await executor.executeContractTransaction( - currentBc, 'cancel', { from: accounts[2], autoGas: 1.1, }); + currentBc, 'cancel', { from: accounts[2], autoGas: 1.1 }, + ); const isMember = await executor.executeContractCall( - currentBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + currentBc, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); expect(isMember).to.be.false; - }) + }); }); describe('when working with a InviteOnly Business Center', async () => { it('rejects a join', async () => { const joinPromise = executor.executeContractTransaction( - currentBc, 'join', { from: accounts[2], autoGas: 1.1, }); + currentBc, 'join', { from: accounts[2], autoGas: 1.1 }, + ); expect(joinPromise).to.be.rejected; }); it('adds a member, when a member is invited', async () => { await executor.executeContractTransaction( - currentBc, 'invite', { from: accounts[0], autoGas: 1.1, }, accounts[1], ); - let isMember = await executor.executeContractCall(currentBc, 'isMember', accounts[1]); + currentBc, 'invite', { from: accounts[0], autoGas: 1.1 }, accounts[1], + ); + const isMember = await executor.executeContractCall(currentBc, 'isMember', accounts[1]); expect(isMember).to.be.true; }); }); @@ -234,41 +262,50 @@ describe('Business Center', function() { it('allows to to cancel membership', async () => { let isMember = await executor.executeContractCall( - businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); if (!isMember) { await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[2], autoGas: 1.1, }); + businessCenter, 'join', { from: accounts[2], autoGas: 1.1 }, + ); } await executor.executeContractTransaction( - businessCenter, 'cancel', { from: accounts[2], autoGas: 1.1, }); + businessCenter, 'cancel', { from: accounts[2], autoGas: 1.1 }, + ); isMember = await executor.executeContractCall( - businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); expect(isMember).to.be.false; }); it('does not allow to cancel a membership if not joined', async () => { - let isMember = await executor.executeContractCall( - businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + const isMember = await executor.executeContractCall( + businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); if (isMember) { await executor.executeContractTransaction( - businessCenter, 'cancel', { from: accounts[2], autoGas: 1.1, }); + businessCenter, 'cancel', { from: accounts[2], autoGas: 1.1 }, + ); } const promise = executor.executeContractTransaction( - businessCenter, 'cancel', { from: accounts[2], autoGas: 1.1, }); + businessCenter, 'cancel', { from: accounts[2], autoGas: 1.1 }, + ); await expect(promise).to.be.rejected; }); it('does not allow sending fake contract events', async () => { - let isMember = await executor.executeContractCall( - businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + const isMember = await executor.executeContractCall( + businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); if (isMember) { await executor.executeContractTransaction( - businessCenter, 'cancel', { from: accounts[2], autoGas: 1.1, }); + businessCenter, 'cancel', { from: accounts[2], autoGas: 1.1 }, + ); } const promise = executor.executeContractTransaction( businessCenter, 'sendContractEvent', - { from: accounts[2], autoGas: 1.1, }, + { from: accounts[2], autoGas: 1.1 }, 1, empty, accounts[2], @@ -278,76 +315,94 @@ describe('Business Center', function() { it('allows members to set their own profile', async () => { const sampleProfile = '0x1234000000000000000000000000000000000000000000000000000000000000'; - let isMember = await executor.executeContractCall( - businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + const isMember = await executor.executeContractCall( + businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); if (!isMember) { await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[2], autoGas: 1.1, }); + businessCenter, 'join', { from: accounts[2], autoGas: 1.1 }, + ); } let profile = await executor.executeContractCall(businessCenter, 'getProfile', accounts[2]); if (profile !== empty) { await executor.executeContractTransaction( - businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1, }, empty); + businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1 }, empty, + ); } await executor.executeContractTransaction( - businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1, }, sampleProfile); + businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1 }, sampleProfile, + ); profile = await executor.executeContractCall(businessCenter, 'getProfile', accounts[2]); expect(profile).to.eq(sampleProfile); }); it('removes a user profile when this user leaves', async () => { const sampleProfile = '0x1234000000000000000000000000000000000000000000000000000000000000'; - let isMember = await executor.executeContractCall( - businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + const isMember = await executor.executeContractCall( + businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); if (!isMember) { await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[2], autoGas: 1.1, }); + businessCenter, 'join', { from: accounts[2], autoGas: 1.1 }, + ); } await executor.executeContractTransaction( - businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1, }, sampleProfile); + businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1 }, sampleProfile, + ); let profile = await executor.executeContractCall( - businessCenter, 'getProfile', accounts[2]); + businessCenter, 'getProfile', accounts[2], + ); expect(profile).to.eq(sampleProfile); await executor.executeContractTransaction( - businessCenter, 'cancel', { from: accounts[2], autoGas: 1.1, }); + businessCenter, 'cancel', { from: accounts[2], autoGas: 1.1 }, + ); profile = await executor.executeContractCall(businessCenter, 'getProfile', accounts[2]); expect(profile).to.eq(empty); }); it('does not allow setting a profile when executing user is not a member ', async () => { const sampleProfile = '0x1234000000000000000000000000000000000000000000000000000000000000'; - let isMember = await executor.executeContractCall( - businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + const isMember = await executor.executeContractCall( + businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); if (isMember) { await executor.executeContractTransaction( - businessCenter, 'cancel', { from: accounts[2], autoGas: 1.1, }); + businessCenter, 'cancel', { from: accounts[2], autoGas: 1.1 }, + ); } const promise = executor.executeContractTransaction( - businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1, }, sampleProfile); + businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1 }, sampleProfile, + ); await expect(promise).to.be.rejected; }); it('allows members to update own profile', async () => { const sampleProfile1 = '0x1234000000000000000000000000000000000000000000000000000000000000'; const sampleProfile2 = '0x1234500000000000000000000000000000000000000000000000000000000000'; - let isMember = await executor.executeContractCall( - businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000, }); + const isMember = await executor.executeContractCall( + businessCenter, 'isMember', accounts[2], { from: accounts[2], gas: 3000000 }, + ); if (!isMember) { await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[2], autoGas: 1.1, }); + businessCenter, 'join', { from: accounts[2], autoGas: 1.1 }, + ); } let profile = await executor.executeContractCall(businessCenter, 'getProfile', accounts[2]); if (profile !== empty) { await executor.executeContractTransaction( - businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1, }, empty); + businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1 }, empty, + ); } await executor.executeContractTransaction( - businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1, }, sampleProfile1); + businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1 }, sampleProfile1, + ); profile = await executor.executeContractCall( - businessCenter, 'getProfile', accounts[2]); + businessCenter, 'getProfile', accounts[2], + ); expect(profile).to.eq(sampleProfile1); await executor.executeContractTransaction( - businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1, }, sampleProfile2); + businessCenter, 'setMyProfile', { from: accounts[2], autoGas: 1.1 }, sampleProfile2, + ); profile = await executor.executeContractCall(businessCenter, 'getProfile', accounts[2]); expect(profile).to.eq(sampleProfile2); }); diff --git a/src/contracts/data-contract/data-contract.spec.ts b/src/contracts/data-contract/data-contract.spec.ts index 85e6fd4c..4fd55909 100644 --- a/src/contracts/data-contract/data-contract.spec.ts +++ b/src/contracts/data-contract/data-contract.spec.ts @@ -18,34 +18,30 @@ */ import 'mocha'; +import * as chaiAsPromised from 'chai-as-promised'; import { expect, use } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); import { ContractLoader, - Description, DfsInterface, Envelope, - EventHub, Executor, NameResolver, } from '@evan.network/dbcp'; import { accounts } from '../../test/accounts'; import { ConsumerState, ContractState } from '../base-contract/base-contract'; -import { configTestcore as config } from '../../config-testcore'; import { CryptoProvider } from '../../encryption/crypto-provider'; import { DataContract } from './data-contract'; -import { Sharing } from '../../contracts/sharing'; +import { Sharing } from '../sharing'; import { sampleContext, TestUtils } from '../../test/test-utils'; use(chaiAsPromised); -describe('DataContract', function() { +describe('DataContract', function test() { this.timeout(60000); let dc: DataContract; - let contractFactory: any; let executor: Executor; let loader: ContractLoader; let businessCenterDomain; @@ -63,16 +59,14 @@ describe('DataContract', function() { '0x0000000000000000000000000000000000000000000000000000000000041234', '0x0000000000000000000000000000000000000000000000000000000000051234', ]; - /* tslint:disable:quotemark */ const sampleDescription: Envelope = { - "public": { - "name": "Data Contract Sample", - "description": "reiterance oxynitrate sat alternize acurative", - "version": "0.1.0", - "author": "evan GmbH", - } + public: { + name: 'Data Contract Sample', + description: 'reiterance oxynitrate sat alternize acurative', + version: '0.1.0', + author: 'evan GmbH', + }, }; - /* tslint:enable:quotemark */ before(async () => { web3 = TestUtils.getWeb3(); @@ -97,25 +91,6 @@ describe('DataContract', function() { description: await TestUtils.getDescription(web3, dfs), }); businessCenterDomain = null; - /* nameResolver.getDomainName(config.nameResolver.domains.businessCenter); - - const businessCenterAddress = await nameResolver.getAddress(businessCenterDomain); - const businessCenter = await loader.loadContract('BusinessCenter', businessCenterAddress); - if (!await executor.executeContractCall( - businessCenter, 'isMember', accounts[0], { from: accounts[0], })) { - await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[0], autoGas: 1.1, }); - } - if (!await executor.executeContractCall( - businessCenter, 'isMember', accounts[1], { from: accounts[1], })) { - await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[1], autoGas: 1.1, }); - } - if (!await executor.executeContractCall( - businessCenter, 'isMember', accounts[2], { from: accounts[2], })) { - await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[2], autoGas: 1.1, }); - }*/ }); async function createContract(addSharing = false, schema?) { @@ -127,20 +102,23 @@ describe('DataContract', function() { .getCryptorByCryptoAlgo('aes').getCryptoInfo(nameResolver.soliditySha3(accounts[0])); } const contract = await dc.create( - 'testdatacontract', accounts[0], businessCenterDomain, description); + 'testdatacontract', accounts[0], businessCenterDomain, description, + ); await dc.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[1]); + businessCenterDomain, contract.options.address, accounts[0], accounts[1], + ); if (addSharing) { const blockNr = await web3.eth.getBlockNumber(); const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', blockNr); await sharing.addSharing( - contract.options.address, accounts[0], accounts[1], '*', blockNr, contentKey); + contract.options.address, accounts[0], accounts[1], '*', blockNr, contentKey, + ); } return contract; } async function runTestSubset(useClassicRawMode) { - const [ storeInDfs, encryptHashes ] = [...Array(2)].map(() => !useClassicRawMode); + const [storeInDfs, encryptHashes] = [...Array(2)].map(() => !useClassicRawMode); describe('when working with entries', async () => { describe('that can only be set by the owner', async () => { it('allows the owner to add and get entries', async () => { @@ -154,7 +132,8 @@ describe('DataContract', function() { encryptHashes, ); const retrieved = await dc.getEntry( - contract, 'entry_settable_by_owner', accounts[0], storeInDfs, encryptHashes); + contract, 'entry_settable_by_owner', accounts[0], storeInDfs, encryptHashes, + ); expect(retrieved).to.eq(sampleValues[0]); }); it('does not allow the member to add and get entries', async () => { @@ -190,10 +169,9 @@ describe('DataContract', function() { storeInDfs, encryptHashes, ); - const retrievedDefaultAlgo = await dc.getEntry( - contract, 'entry_settable_by_owner', accounts[0], storeInDfs, encryptHashes); const encryptedHash = await executor.executeContractCall( - contract, 'getEntry', nameResolver.sha3('entry_settable_by_owner')); + contract, 'getEntry', nameResolver.sha3('entry_settable_by_owner'), + ); if (!storeInDfs) { expect(encryptedHash).to.eq(sampleValues[0]); } else { @@ -213,10 +191,9 @@ describe('DataContract', function() { encryptHashes, 'aes', ); - const retrievedAes = await dc.getEntry( - contract, 'entry_settable_by_owner', accounts[0], storeInDfs, encryptHashes); const hashAes = await executor.executeContractCall( - contract, 'getEntry', nameResolver.sha3('entry_settable_by_owner')); + contract, 'getEntry', nameResolver.sha3('entry_settable_by_owner'), + ); if (!storeInDfs) { expect(hashAes).to.eq(sampleValues[0]); } else { @@ -236,10 +213,9 @@ describe('DataContract', function() { encryptHashes, 'unencrypted', ); - const retrievedUnencrypted = await dc.getEntry( - contract, 'entry_settable_by_owner', accounts[0], storeInDfs, encryptHashes); const hashRaw = await executor.executeContractCall( - contract, 'getEntry', nameResolver.sha3('entry_settable_by_owner')); + contract, 'getEntry', nameResolver.sha3('entry_settable_by_owner'), + ); if (!storeInDfs) { expect(hashRaw).to.eq(sampleValues[0]); } else { @@ -268,7 +244,8 @@ describe('DataContract', function() { // get key to share const blockNr = await web3.eth.getBlockNumber(); const contentKey = await sharing.getKey( - contract.options.address, accounts[0], '*', blockNr); + contract.options.address, accounts[0], '*', blockNr, + ); // add context based sharing key await sharing.addSharing( @@ -281,9 +258,10 @@ describe('DataContract', function() { sampleContext, ); const retrieved = await dc.getEntry( - contract, 'entry_settable_by_owner', sampleContext, storeInDfs, encryptHashes); + contract, 'entry_settable_by_owner', sampleContext, storeInDfs, encryptHashes, + ); expect(retrieved).to.eq(sampleValues[0]); - }) + }); }); describe('that can be set by owner and member', async () => { it('allows the owner to add and get entries', async () => { @@ -297,7 +275,8 @@ describe('DataContract', function() { encryptHashes, ); const retrieved = await dc.getEntry( - contract, 'entry_settable_by_member', accounts[0], storeInDfs, encryptHashes); + contract, 'entry_settable_by_member', accounts[0], storeInDfs, encryptHashes, + ); expect(retrieved).to.eq(sampleValues[0]); }); it('allows the member to add and get entries', async () => { @@ -311,7 +290,8 @@ describe('DataContract', function() { encryptHashes, ); const retrieved = await dc.getEntry( - contract, 'entry_settable_by_member', accounts[1], storeInDfs, encryptHashes); + contract, 'entry_settable_by_member', accounts[1], storeInDfs, encryptHashes, + ); expect(retrieved).to.eq(sampleValues[0]); }); }); @@ -329,8 +309,9 @@ describe('DataContract', function() { encryptHashes, ); const retrieved = await dc.getListEntries( - contract, 'list_settable_by_owner', accounts[0], storeInDfs, encryptHashes); - for (let i = 0; i < sampleValues.length; i++) { + contract, 'list_settable_by_owner', accounts[0], storeInDfs, encryptHashes, + ); + for (let i = 0; i < sampleValues.length; i += 1) { expect(retrieved[i]).to.eq(sampleValues[i]); } }); @@ -365,7 +346,7 @@ describe('DataContract', function() { 2, ); expect(retrieved.length).to.eq(2); - for (let i = 0; i < 2; i++) { + for (let i = 0; i < 2; i += 1) { expect(retrieved[i]).to.eq(sampleValues[i]); } }); @@ -380,9 +361,10 @@ describe('DataContract', function() { encryptHashes, ); const retrieved = await dc.getListEntries( - contract, 'list_settable_by_owner', accounts[0], storeInDfs, encryptHashes, 10, 2); + contract, 'list_settable_by_owner', accounts[0], storeInDfs, encryptHashes, 10, 2, + ); expect(retrieved.length).to.eq(sampleValues.length - 2); - for (let i = 0; i < retrieved.length; i++) { + for (let i = 0; i < retrieved.length; i += 1) { expect(retrieved[i]).to.eq(sampleValues[i + 2]); } }); @@ -407,7 +389,7 @@ describe('DataContract', function() { true, ); const reverseSamples = sampleValues.reverse(); - for (let i = 0; i < retrieved.length; i++) { + for (let i = 0; i < retrieved.length; i += 1) { expect(retrieved[i]).to.eq(reverseSamples[i]); } }); @@ -432,7 +414,7 @@ describe('DataContract', function() { true, ); const reverseSamples = sampleValues.reverse(); - for (let i = 0; i < 2; i++) { + for (let i = 0; i < 2; i += 1) { expect(retrieved[i]).to.eq(reverseSamples[i + 1]); } }); @@ -455,7 +437,7 @@ describe('DataContract', function() { storeInDfs, encryptHashes, ); - for (let i = 0; i < sampleValues.length; i++) { + for (let i = 0; i < sampleValues.length; i += 1) { expect(retrieved[i]).to.eq(sampleValues[i]); } }); @@ -476,7 +458,7 @@ describe('DataContract', function() { storeInDfs, encryptHashes, ); - for (let i = 0; i < sampleValues.length; i++) { + for (let i = 0; i < sampleValues.length; i += 1) { expect(retrieved[i]).to.eq(sampleValues[i]); } }); @@ -493,17 +475,19 @@ describe('DataContract', function() { encryptHashes, ); let retrieved = await dc.getListEntries( - contract, 'list_removable_by_owner', accounts[0], storeInDfs, encryptHashes); - for (let i = 0; i < sampleValues.length; i++) { + contract, 'list_removable_by_owner', accounts[0], storeInDfs, encryptHashes, + ); + for (let i = 0; i < sampleValues.length; i += 1) { expect(retrieved[i]).to.eq(sampleValues[i]); } await dc.removeListEntry(contract, 'list_removable_by_owner', 2, accounts[0]); retrieved = await dc.getListEntries( - contract, 'list_removable_by_owner', accounts[0], storeInDfs, encryptHashes); + contract, 'list_removable_by_owner', accounts[0], storeInDfs, encryptHashes, + ); // handle array values like the datacontract const modifiedSampleValues = sampleValues.slice(0); modifiedSampleValues[2] = modifiedSampleValues[modifiedSampleValues.length - 1]; - for (let i = 0; i < (modifiedSampleValues.length - 1) ; i++) { + for (let i = 0; i < (modifiedSampleValues.length - 1); i += 1) { expect(retrieved[i]).to.eq(modifiedSampleValues[i]); } }); @@ -517,9 +501,10 @@ describe('DataContract', function() { storeInDfs, encryptHashes, ); - let retrieved = await dc.getListEntries( - contract, 'list_removable_by_owner', accounts[0], storeInDfs, encryptHashes); - for (let i = 0; i < sampleValues.length; i++) { + const retrieved = await dc.getListEntries( + contract, 'list_removable_by_owner', accounts[0], storeInDfs, encryptHashes, + ); + for (let i = 0; i < sampleValues.length; i += 1) { expect(retrieved[i]).to.eq(sampleValues[i]); } const promise = dc.removeListEntry(contract, 'list_removable_by_owner', 2, accounts[1]); @@ -527,42 +512,42 @@ describe('DataContract', function() { }); }); describe('when working with descriptions', async () => { - /* tslint:disable:quotemark */ const testSchema = { list_settable_by_member: { - "$id": "list_settable_by_member_schema", - "type": "object", - "additionalProperties": false, - "properties": { - "foo": { "type": "string" }, - "bar": { "type": "integer" } - } + $id: 'list_settable_by_member_schema', + type: 'object', + additionalProperties: false, + properties: { + foo: { type: 'string' }, + bar: { type: 'integer' }, + }, }, entry_settable_by_member: { - "$id": "entry_settable_by_member_schema", - "type": "integer", - } + $id: 'entry_settable_by_member_schema', + type: 'integer', + }, }; - /* tslint:enable:quotemark */ it('allows adding entries matching the field schema', async () => { const contract = await createContract(!storeInDfs, testSchema); - const values = [ !storeInDfs ? sampleValues[0] : { + const values = [!storeInDfs ? sampleValues[0] : { foo: 'sample', bar: 123, }]; const promise = dc.addListEntries( - contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes); + contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes, + ); await expect(promise).to.be.fulfilled; }); it('forbids adding entries not matching the field schema', async () => { const contract = await createContract(!storeInDfs, testSchema); - const values = [ !storeInDfs ? sampleValues[0] : { + const values = [!storeInDfs ? sampleValues[0] : { foo: 'sample', bar: 'totally not a number', barz: 123, }]; const promise = dc.addListEntries( - contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes); + contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes, + ); if (!storeInDfs) { await expect(promise).to.be.fulfilled; } else { @@ -571,12 +556,13 @@ describe('DataContract', function() { }); it('forbids adding entries not matching their type', async () => { const contract = await createContract(!storeInDfs, testSchema); - const values = [ !storeInDfs ? sampleValues[0] : { + const values = [!storeInDfs ? sampleValues[0] : { foo: 'sample', bar: '123', }]; const promise = dc.addListEntries( - contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes); + contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes, + ); if (!storeInDfs) { await expect(promise).to.be.fulfilled; } else { @@ -585,13 +571,14 @@ describe('DataContract', function() { }); it('forbids adding entries with more properties than defined', async () => { const contract = await createContract(!storeInDfs, testSchema); - const values = [ !storeInDfs ? sampleValues[0] : { + const values = [!storeInDfs ? sampleValues[0] : { foo: 'sample', bar: 123, barz: 123, }]; const promise = dc.addListEntries( - contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes); + contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes, + ); if (!storeInDfs) { await expect(promise).to.be.fulfilled; } else { @@ -600,26 +587,29 @@ describe('DataContract', function() { }); it('fallbacks to accept any if no schema was found', async () => { const contract = await createContract(storeInDfs); - const values = [ !storeInDfs ? sampleValues[0] : { + const values = [!storeInDfs ? sampleValues[0] : { foo: 'sample', barz: 123, }]; const promise = dc.addListEntries( - contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes); + contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes, + ); await expect(promise).to.be.fulfilled; }); it('allows setting entries matching the field schema', async () => { const contract = await createContract(!storeInDfs, testSchema); const value = !storeInDfs ? sampleValues[0] : 123; const promise = dc.setEntry( - contract, 'entry_settable_by_member', value, accounts[0], storeInDfs, encryptHashes); + contract, 'entry_settable_by_member', value, accounts[0], storeInDfs, encryptHashes, + ); await expect(promise).to.be.fulfilled; }); it('forbids setting entries not matching the field schema', async () => { const contract = await createContract(!storeInDfs, testSchema); const value = !storeInDfs ? sampleValues[0] : 'totally not an integer'; const promise = dc.setEntry( - contract, 'entry_settable_by_member', value, accounts[0], storeInDfs, encryptHashes); + contract, 'entry_settable_by_member', value, accounts[0], storeInDfs, encryptHashes, + ); if (!storeInDfs) { await expect(promise).to.be.fulfilled; } else { @@ -629,38 +619,38 @@ describe('DataContract', function() { it('allows specifying item based schemata', async () => { const contract = await createContract(!storeInDfs, testSchema); - const values = [ !storeInDfs ? sampleValues[0] : { + const values = [!storeInDfs ? sampleValues[0] : { foo: 'sample', bar: 123, }]; const promise = dc.addListEntries( - contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes); + contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes, + ); await expect(promise).to.be.fulfilled; }); it('allows specifying list based schemata', async () => { const customSchema = JSON.parse(JSON.stringify(testSchema)); - /* tslint:disable:quotemark */ customSchema.list_settable_by_member = { - "$id": "list_settable_by_member_schema", - "$comment": "{\"entryType\": \"list\"}", - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "foo": { "type": "string" }, - "bar": { "type": "integer" } - } - } + $id: 'list_settable_by_member_schema', + $comment: '{"entryType": "list"}', + type: 'array', + items: { + type: 'object', + additionalProperties: false, + properties: { + foo: { type: 'string' }, + bar: { type: 'integer' }, + }, + }, }; - /* tslint:enable:quotemark */ const contract = await createContract(!storeInDfs, customSchema); - const values = [ !storeInDfs ? sampleValues[0] : { + const values = [!storeInDfs ? sampleValues[0] : { foo: 'sample', bar: 123, }]; const promise = dc.addListEntries( - contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes); + contract, 'list_settable_by_member', values, accounts[0], storeInDfs, encryptHashes, + ); await expect(promise).to.be.fulfilled; }); }); @@ -676,18 +666,20 @@ describe('DataContract', function() { encryptHashes, ); let retrieved = await dc.getListEntries( - contract, 'list_settable_by_owner', accounts[0], storeInDfs, encryptHashes); - for (let i = 0; i < sampleValues.length; i++) { + contract, 'list_settable_by_owner', accounts[0], storeInDfs, encryptHashes, + ); + for (let i = 0; i < sampleValues.length; i += 1) { expect(retrieved[i]).to.eq(sampleValues[i]); } retrieved = await dc.getListEntries( - contract, 'list_settable_by_member', accounts[0], storeInDfs, encryptHashes); - for (let i = 0; i < sampleValues.length; i++) { + contract, 'list_settable_by_member', accounts[0], storeInDfs, encryptHashes, + ); + for (let i = 0; i < sampleValues.length; i += 1) { expect(retrieved[i]).to.eq(sampleValues[i]); } }); - it('does not allow the member to add entries in all lists ' + - 'if not permitted to access one of them', + it('does not allow the member to add entries in all lists ' + + 'if not permitted to access one of them', async () => { const contract = await createContract(storeInDfs); const promise = dc.addListEntries( @@ -710,7 +702,8 @@ describe('DataContract', function() { encryptHashes, ); expect(await dc.getListEntryCount( - contract, 'list_removable_by_owner')).to.eq(sampleValues.length); + contract, 'list_removable_by_owner', + )).to.eq(sampleValues.length); expect(await dc.getListEntryCount(contract, 'list_settable_by_member')).to.eq(0); expect(await dc.getListEntry( contract, @@ -722,7 +715,8 @@ describe('DataContract', function() { )).to.eq(sampleValues[1]); // move item await dc.moveListEntry( - contract, 'list_removable_by_owner', 1, ['list_settable_by_member'], accounts[0]); + contract, 'list_removable_by_owner', 1, ['list_settable_by_member'], accounts[0], + ); expect(await dc.getListEntryCount(contract, 'list_removable_by_owner')) .to.eq(sampleValues.length - 1); expect(await dc.getListEntryCount(contract, 'list_settable_by_member')).to.eq(1); @@ -745,10 +739,9 @@ describe('DataContract', function() { encryptHashes, )).to.eq(sampleValues[1]); }); - it('allows to move an entry from one list to multiple lists', async () => {}); }); }); - describe('when working with mappings', async() => { + describe('when working with mappings', async () => { // key types are basically irrelevant, will be hashed anyway const sampleMappingKeys = [...accounts]; describe('that can only be set by the owner', async () => { @@ -973,8 +966,8 @@ describe('DataContract', function() { const promise = dc.changeContractState(contract, accounts[0], ContractState.Approved); await expect(promise).to.be.rejected; }); - it('does not allow to change the state ' + - 'with a user without contract state update permission', + it('does not allow to change the state ' + + 'with a user without contract state update permission', async () => { const contract = await createContract(true); const promise = dc.changeContractState(contract, accounts[1], ContractState.PendingApproval); @@ -991,7 +984,8 @@ describe('DataContract', function() { const contract = await createContract(true); // owners current state is 'Draft', so going to 'Terminated' is not allowed const promise = dc.changeConsumerState( - contract, accounts[0], accounts[0], ConsumerState.Terminated); + contract, accounts[0], accounts[0], ConsumerState.Terminated, + ); await expect(promise).to.be.rejected; }); }); @@ -1005,7 +999,8 @@ describe('DataContract', function() { const contract = await createContract(true); // members current state is 'Draft', owner can set its state 'Active' const promise = dc.changeConsumerState( - contract, accounts[0], accounts[1], ConsumerState.Active); + contract, accounts[0], accounts[1], ConsumerState.Active, + ); await expect(promise).to.be.rejected; }); }); diff --git a/src/contracts/data-contract/data-contract.ts b/src/contracts/data-contract/data-contract.ts index 02787fcb..d853351b 100644 --- a/src/contracts/data-contract/data-contract.ts +++ b/src/contracts/data-contract/data-contract.ts @@ -17,35 +17,33 @@ the following URL: https://evan.network/license/ */ -import crypto = require('crypto'); -import prottle = require('prottle'); +import * as crypto from 'crypto'; +import * as prottle from 'prottle'; import { - Description, DfsInterface, Envelope, - Logger, Validator, } from '@evan.network/dbcp'; import { BaseContract, BaseContractOptions } from '../base-contract/base-contract'; import { CryptoProvider } from '../../encryption/crypto-provider'; +import { Description } from '../../shared-description'; import { Sharing } from '../sharing'; const requestWindowSize = 10; - /** * options for DataContract constructor */ export interface DataContractOptions extends BaseContractOptions { - cryptoProvider: CryptoProvider, - dfs: DfsInterface, - sharing: Sharing, - web3: any, - defaultCryptoAlgo?: string, - description: Description, + cryptoProvider: CryptoProvider; + dfs: DfsInterface; + sharing: Sharing; + web3: any; + defaultCryptoAlgo?: string; + description: Description; } /** @@ -55,12 +53,16 @@ export interface DataContractOptions extends BaseContractOptions { */ export class DataContract extends BaseContract { protected options: DataContractOptions; + private readonly encodingUnencrypted = 'utf-8'; + private readonly encodingEncrypted = 'hex'; + private readonly encodingUnencryptedHash = 'hex'; + private readonly cryptoAlgorithHashes = 'aesEcb'; - constructor(optionsInput: DataContractOptions) { + public constructor(optionsInput: DataContractOptions) { super(optionsInput as BaseContractOptions); this.options = optionsInput; if (!this.options.defaultCryptoAlgo) { @@ -84,37 +86,46 @@ export class DataContract extends BaseContract { * @return {Promise} contract instance */ public async create( - factoryName: string, - accountId: string, - businessCenterDomain?: string, - contractDescription: any = '0x0000000000000000000000000000000000000000000000000000000000000000', - allowConsumerInvite = true, - sharingsHash = null, + factoryName: string, + accountId: string, + businessCenterDomain?: string, + contractDescription: any = '0x0000000000000000000000000000000000000000000000000000000000000000', + allowConsumerInvite = true, + sharingsHash = null, ): Promise { const contractP = (async () => { - const descriptionHash = (typeof contractDescription === 'object') ? - '0x0000000000000000000000000000000000000000000000000000000000000000' : contractDescription; + const descriptionHash = (typeof contractDescription === 'object') + ? '0x0000000000000000000000000000000000000000000000000000000000000000' : contractDescription; const contractId = await super.createUninitialized( - factoryName, accountId, businessCenterDomain, descriptionHash); + factoryName, accountId, businessCenterDomain, descriptionHash, + ); const contractInterface = this.options.loader.loadContract('DataContractInterface', contractId); const rootDomain = this.options.nameResolver.namehash( - this.options.nameResolver.getDomainName(this.options.nameResolver.config.domains.root)); + this.options.nameResolver.getDomainName(this.options.nameResolver.config.domains.root), + ); await this.options.executor.executeContractTransaction( contractInterface, 'init', - { from: accountId, autoGas: 1.1, }, + { from: accountId, autoGas: 1.1 }, rootDomain, allowConsumerInvite, ); return contractInterface; })(); - const [contract, sharingInfo] = await Promise.all([contractP, sharingsHash ? { sharingsHash, } : this.createSharing(accountId)]); + const [contract, sharingInfo] = await Promise.all( + [contractP, sharingsHash ? { sharingsHash } : this.createSharing(accountId)], + ); if (sharingInfo.sharingsHash !== '0x0000000000000000000000000000000000000000000000000000000000000000') { await this.options.executor.executeContractTransaction( - contract, 'setSharing', { from: accountId, autoGas: 1.1, }, sharingInfo.sharingsHash); + contract, 'setSharing', { from: accountId, autoGas: 1.1 }, sharingInfo.sharingsHash, + ); } if (typeof contractDescription === 'object') { - await this.options.description.setDescriptionToContract(contract.options.address, contractDescription, accountId); + await this.options.description.setDescriptionToContract( + contract.options.address, + contractDescription, + accountId, + ); } return contract; } @@ -127,12 +138,17 @@ export class DataContract extends BaseContract { */ public async createSharing(accountId: string, skipUpload = false): Promise { // create sharing key for owner - const cryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo(this.options.defaultCryptoAlgo); - const hashCryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo(this.cryptoAlgorithHashes); + const cryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo( + this.options.defaultCryptoAlgo, + ); + const hashCryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo( + this.cryptoAlgorithHashes, + ); const [contentKey, hashKey, blockNr] = await Promise.all( - [cryptor.generateKey(), hashCryptor.generateKey(), this.options.web3.eth.getBlockNumber()]); + [cryptor.generateKey(), hashCryptor.generateKey(), this.options.web3.eth.getBlockNumber()], + ); - const sharings = {}; + const sharings = {}; await this.options.sharing.extendSharings(sharings, accountId, accountId, '*', blockNr, contentKey); await this.options.sharing.extendSharings(sharings, accountId, accountId, '*', 'hashKey', hashKey); @@ -162,69 +178,96 @@ export class DataContract extends BaseContract { * @return {Promise} resolved when done */ public async addListEntries( - contract: any|string, - listName: string|string[], - values: any[], - accountId: string, - dfsStorage = true, - encryptedHashes = true, - encryption: string = this.options.defaultCryptoAlgo, - encryptionContext = accountId): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + contract: any|string, + listName: string|string[], + values: any[], + accountId: string, + dfsStorage = true, + encryptedHashes = true, + encryption: string = this.options.defaultCryptoAlgo, + encryptionContext = accountId, + ): Promise { + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('DataContractInterface', contract); const listNames = Array.isArray(listName) ? listName : [listName]; let hashes = values; if (!dfsStorage) { if (encryptedHashes) { - hashes = await Promise.all(hashes.map(hash => this.encryptHash(hash, dataContract, encryptionContext))); + hashes = await Promise.all( + hashes.map((hash) => this.encryptHash(hash, dataContract, encryptionContext)), + ); } // store as is await this.options.executor.executeContractTransaction( dataContract, 'addListEntries', - { from: accountId, autoGas: 1.1, }, - listNames.map(name => this.options.web3.utils.sha3(name)), + { from: accountId, autoGas: 1.1 }, + listNames.map((name) => this.options.web3.utils.sha3(name)), hashes, ); } else { // upload to ipfs - const [ description, blockNr ] = await Promise.all([ - this.options.description.getDescriptionFromContract(dataContract.options.address, encryptionContext), + const [description, blockNr] = await Promise.all([ + this.options.description.getDescriptionFromContract( + dataContract.options.address, + encryptionContext, + ), this.options.web3.eth.getBlockNumber(), ]); - await Promise.all((listNames).map(name => this.validate(description, name, hashes))); + await Promise.all((listNames).map((name) => this.validate(description, name, hashes))); // get all keys and check if they differ - const keys = await Promise.all(listNames.map(name => - this.options.sharing.getKey(dataContract.options.address, encryptionContext, name, blockNr))); + const keys = await Promise.all( + listNames.map((name) => this.options.sharing.getKey( + dataContract.options.address, + encryptionContext, + name, + blockNr, + )), + ); const groupedKeys = {}; keys.forEach((key, index) => { if (groupedKeys[key]) { groupedKeys[key].push(listNames[index]); } else { - groupedKeys[key] = [ listNames[index] ]; + groupedKeys[key] = [listNames[index]]; } }); // push grouped by key - for (let key of Object.keys(groupedKeys)) { + for (let i = 0; i < Object.keys(groupedKeys).length; i += 1) { + const key = Object.keys(groupedKeys)[i]; const ipfsFiles = []; - for (let value of hashes) { - const encrypted = await this.encrypt({private: value}, dataContract, encryptionContext, groupedKeys[key][0], blockNr, encryption); + for (let j = 0; j < hashes.length; j += 1) { + const value = hashes[j]; + // eslint-disable-next-line no-await-in-loop + const encrypted = await this.encrypt( + { private: value }, + dataContract, + encryptionContext, + groupedKeys[key][0], + blockNr, + encryption, + ); const stateMd5 = crypto.createHash('md5').update(encrypted).digest('hex'); ipfsFiles.push({ path: stateMd5, content: Buffer.from(encrypted), }); - }; + } + // eslint-disable-next-line no-await-in-loop hashes = await this.options.dfs.addMultiple(ipfsFiles); if (encryptedHashes) { - hashes = await Promise.all(hashes.map(hash => this.encryptHash(hash, dataContract, encryptionContext))); + // eslint-disable-next-line no-await-in-loop + hashes = await Promise.all( + hashes.map((hash) => this.encryptHash(hash, dataContract, encryptionContext)), + ); } + // eslint-disable-next-line no-await-in-loop await this.options.executor.executeContractTransaction( dataContract, 'addListEntries', - { from: accountId, autoGas: 1.1, }, - groupedKeys[key].map(name => this.options.web3.utils.sha3(name)), + { from: accountId, autoGas: 1.1 }, + groupedKeys[key].map((name) => this.options.web3.utils.sha3(name)), hashes, ); } @@ -248,9 +291,14 @@ export class DataContract extends BaseContract { * @param {string} propertyName property in contract that is decrypted * @return {Promise} decrypted envelope */ - public async decrypt(toDecrypt: string, contract: any, accountId: string, propertyName: string): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + public async decrypt( + toDecrypt: string, + contract: any, + accountId: string, + propertyName: string, + ): Promise { + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('DataContractInterface', contract); // decode envelope const envelope: Envelope = JSON.parse(toDecrypt); if (envelope.cryptoInfo) { @@ -269,7 +317,8 @@ export class DataContract extends BaseContract { } const decryptedBuffer = await cryptor.decrypt( - Buffer.from(envelope.private, this.encodingEncrypted), { key: contentKey, }); + Buffer.from(envelope.private, this.encodingEncrypted), { key: contentKey }, + ); envelope.private = decryptedBuffer; } return envelope; @@ -284,8 +333,8 @@ export class DataContract extends BaseContract { * @return {Promise} decrypted hash */ public async decryptHash(toDecrypt: string, contract: any, accountId: string): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('DataContractInterface', contract); // decode hash const cryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo(this.cryptoAlgorithHashes); const hashKey = await this.options.sharing.getHashKey(dataContract.options.address, accountId); @@ -294,7 +343,8 @@ export class DataContract extends BaseContract { throw new Error(`no hashKey key found for contract "${dataContract.options.address}" and account "${accountId}"`); } const decryptedBuffer = await cryptor.decrypt( - Buffer.from(toDecrypt.substr(2), this.encodingEncrypted), { key: hashKey, }); + Buffer.from(toDecrypt.substr(2), this.encodingEncrypted), { key: hashKey }, + ); return `0x${decryptedBuffer.toString(this.encodingUnencryptedHash)}`; } @@ -315,12 +365,15 @@ export class DataContract extends BaseContract { accountId: string, propertyName: string, block: number, - encryption: string = this.options.defaultCryptoAlgo): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + encryption: string = this.options.defaultCryptoAlgo, + ): Promise { + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('DataContractInterface', contract); // get content key from contract - const contentKey = await this.options.sharing.getKey(dataContract.options.address, accountId, propertyName, block); + const contentKey = await this.options.sharing.getKey( + dataContract.options.address, accountId, propertyName, block, + ); if (!contentKey) { throw new Error(`no content key found for contract "${dataContract.options.address}" and account "${accountId}"`); @@ -328,11 +381,13 @@ export class DataContract extends BaseContract { // encrypt with content key const cryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo(encryption); - const encryptedBuffer = await cryptor.encrypt(toEncrypt.private, { key: contentKey, }); + const encryptedBuffer = await cryptor.encrypt(toEncrypt.private, { key: contentKey }); const encrypted = encryptedBuffer.toString(this.encodingEncrypted); const envelope: Envelope = { private: encrypted, - cryptoInfo: cryptor.getCryptoInfo(this.options.nameResolver.soliditySha3(dataContract.options.address)), + cryptoInfo: cryptor.getCryptoInfo( + this.options.nameResolver.soliditySha3(dataContract.options.address), + ), }; envelope.cryptoInfo.block = block; if (toEncrypt.public) { @@ -350,8 +405,9 @@ export class DataContract extends BaseContract { * @return {Promise} encrypted hash as string */ public async encryptHash(toEncrypt: string, contract: any, accountId: string): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + const dataContract = (typeof contract === 'object') + ? contract + : this.options.loader.loadContract('DataContractInterface', contract); // get hash key from contract const hashKey = await this.options.sharing.getHashKey(dataContract.options.address, accountId); @@ -361,7 +417,10 @@ export class DataContract extends BaseContract { } // encrypt with hashKkey const cryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo(this.cryptoAlgorithHashes); - const encryptedBuffer = await cryptor.encrypt(Buffer.from(toEncrypt.substr(2), this.encodingUnencryptedHash), { key: hashKey, }); + const encryptedBuffer = await cryptor.encrypt( + Buffer.from(toEncrypt.substr(2), this.encodingUnencryptedHash), + { key: hashKey }, + ); return `0x${encryptedBuffer.toString(this.encodingEncrypted)}`; } @@ -376,13 +435,14 @@ export class DataContract extends BaseContract { * @return {Promise} list entries */ public async getEntry( - contract: any|string, - entryName: string, - accountId: string, - dfsStorage = true, - encryptedHashes = true): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + contract: any|string, + entryName: string, + accountId: string, + dfsStorage = true, + encryptedHashes = true, + ): Promise { + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('DataContractInterface', contract); const entryRaw = await this.options.executor.executeContractCall( dataContract, 'getEntry', @@ -398,16 +458,15 @@ export class DataContract extends BaseContract { } if (!dfsStorage) { return hash; - } else { - const encryptedContent = (await this.options.dfs.get(hash)).toString('utf-8'); - const decrypted = await this.decrypt( - encryptedContent, - dataContract, - accountId, - entryName - ); - return decrypted.private; } + const encryptedContent = (await this.options.dfs.get(hash)).toString('utf-8'); + const decrypted = await this.decrypt( + encryptedContent, + dataContract, + accountId, + entryName, + ); + return decrypted.private; } /** @@ -422,14 +481,15 @@ export class DataContract extends BaseContract { * @return {Promise} mappings value for given key */ public async getMappingValue( - contract: any|string, - mappingName: string, - entryName: string, - accountId: string, - dfsStorage = true, - encryptedHashes = true): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + contract: any|string, + mappingName: string, + entryName: string, + accountId: string, + dfsStorage = true, + encryptedHashes = true, + ): Promise { + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('DataContractInterface', contract); const entryRaw = await this.options.executor.executeContractCall( dataContract, 'getMappingValue', @@ -446,16 +506,15 @@ export class DataContract extends BaseContract { } if (!dfsStorage) { return entryRaw; - } else { - const encryptedContent = (await this.options.dfs.get(hash)).toString('utf-8'); - const decrypted = await this.decrypt( - encryptedContent, - dataContract, - accountId, - mappingName - ); - return decrypted.private; } + const encryptedContent = (await this.options.dfs.get(hash)).toString('utf-8'); + const decrypted = await this.decrypt( + encryptedContent, + dataContract, + accountId, + mappingName, + ); + return decrypted.private; } /** @@ -472,16 +531,17 @@ export class DataContract extends BaseContract { * @return {Promise} list entries */ public async getListEntries( - contract: any|string, - listName: string, - accountId: string, - dfsStorage = true, - encryptedHashes = true, - count = 10, - offset = 0, - reverse = false): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + contract: any|string, + listName: string, + accountId: string, + dfsStorage = true, + encryptedHashes = true, + count = 10, + offset = 0, + reverse = false, + ): Promise { + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('DataContractInterface', contract); const listKey = this.options.web3.utils.sha3(listName); const elements = await this.options.nameResolver.getArrayFromUintMapping( @@ -498,22 +558,23 @@ export class DataContract extends BaseContract { } let hashes = elements; if (encryptedHashes) { - hashes = await Promise.all(elements.map(element => this.decryptHash(element, dataContract, accountId))); + hashes = await Promise.all( + elements.map((element) => this.decryptHash(element, dataContract, accountId)), + ); } if (!dfsStorage) { return hashes; - } else { - const envelopes = await prottle(requestWindowSize, hashes.map((hash) => async () => { - const decrypted = await this.decrypt( - (await this.options.dfs.get(hash)).toString('utf-8'), - dataContract, - accountId, - listName - ); - return decrypted; - })); - return envelopes.map(envelope => envelope.private); } + const envelopes = await prottle(requestWindowSize, hashes.map((hash) => async () => { + const decrypted = await this.decrypt( + (await this.options.dfs.get(hash)).toString('utf-8'), + dataContract, + accountId, + listName, + ); + return decrypted; + })); + return envelopes.map((envelope) => envelope.private); } /** @@ -528,14 +589,15 @@ export class DataContract extends BaseContract { * @return {Promise} list entry */ public async getListEntry( - contract: any|string, - listName: string, - index: number, - accountId: string, - dfsStorage = true, - encryptedHashes = true): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + contract: any|string, + listName: string, + index: number, + accountId: string, + dfsStorage = true, + encryptedHashes = true, + ): Promise { + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('DataContractInterface', contract); const listKey = this.options.web3.utils.sha3(listName); const entryRaw = await this.options.executor.executeContractCall(dataContract, 'getListEntry', listKey, index); let hash = entryRaw; @@ -544,15 +606,14 @@ export class DataContract extends BaseContract { } if (!dfsStorage) { return hash; - } else { - const decrypted = await this.decrypt( - (await this.options.dfs.get(hash)).toString('utf-8'), - dataContract, - accountId, - listName - ); - return decrypted.private; } + const decrypted = await this.decrypt( + (await this.options.dfs.get(hash)).toString('utf-8'), + dataContract, + accountId, + listName, + ); + return decrypted.private; } /** @@ -563,10 +624,11 @@ export class DataContract extends BaseContract { * @return {Promise} list entry count */ public async getListEntryCount( - contract: any|string, - listName: string): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + contract: any|string, + listName: string, + ): Promise { + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('DataContractInterface', contract); const listKey = this.options.web3.utils.sha3(listName); return parseInt(await this.options.executor.executeContractCall(dataContract, 'getListEntryCount', listKey), 10); } @@ -582,20 +644,21 @@ export class DataContract extends BaseContract { * @return {Promise} resolved when done */ public async moveListEntry( - contract: any|string, - listNameFrom: string, - entryIndex: number, - listNamesTo: string[], - accountId: string): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + contract: any|string, + listNameFrom: string, + entryIndex: number, + listNamesTo: string[], + accountId: string, + ): Promise { + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('DataContractInterface', contract); await this.options.executor.executeContractTransaction( dataContract, 'moveListEntry', - { from: accountId, gas: 2000000, }, + { from: accountId, gas: 2000000 }, this.options.web3.utils.sha3(listNameFrom), entryIndex, - listNamesTo.map(name => this.options.web3.utils.sha3(name)), + listNamesTo.map((name) => this.options.web3.utils.sha3(name)), ); } @@ -609,16 +672,17 @@ export class DataContract extends BaseContract { * @return {Promise} resolved when done */ public async removeListEntry( - contract: any|string, - listName: string, - entryIndex: number, - accountId: string): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + contract: any|string, + listName: string, + entryIndex: number, + accountId: string, + ): Promise { + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('DataContractInterface', contract); await this.options.executor.executeContractTransaction( dataContract, 'removeListEntry', - { from: accountId, gas: 2000000, }, + { from: accountId, gas: 2000000 }, this.options.web3.utils.sha3(listName), entryIndex, ); @@ -637,28 +701,39 @@ export class DataContract extends BaseContract { * @return {Promise} resolved when done */ public async setEntry( - contract: any|string, - entryName: string, - value: any, - accountId: string, - dfsStorage = true, - encryptedHashes = true, - encryption: string = this.options.defaultCryptoAlgo, - encryptionContext = accountId): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + contract: any|string, + entryName: string, + value: any, + accountId: string, + dfsStorage = true, + encryptedHashes = true, + encryption: string = this.options.defaultCryptoAlgo, + encryptionContext = accountId, + ): Promise { + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('DataContractInterface', contract); let toSet; if (!dfsStorage) { // store as is toSet = value; } else { - const [ description, blockNr ] = await Promise.all([ - this.options.description.getDescriptionFromContract(dataContract.options.address, encryptionContext), + const [description, blockNr] = await Promise.all([ + this.options.description.getDescriptionFromContract( + dataContract.options.address, + encryptionContext, + ), this.options.web3.eth.getBlockNumber(), ]); await this.validate(description, entryName, value); - const encrypted = await this.encrypt({ private: value }, dataContract, encryptionContext, entryName, blockNr, encryption); + const encrypted = await this.encrypt( + { private: value }, + dataContract, + encryptionContext, + entryName, + blockNr, + encryption, + ); const stateMd5 = crypto.createHash('md5').update(encrypted).digest('hex'); toSet = await this.options.dfs.add(stateMd5, Buffer.from(encrypted)); } @@ -668,7 +743,7 @@ export class DataContract extends BaseContract { await this.options.executor.executeContractTransaction( dataContract, 'setEntry', - { from: accountId, autoGas: 1.1, }, + { from: accountId, autoGas: 1.1 }, this.options.web3.utils.sha3(entryName), toSet, ); @@ -688,31 +763,34 @@ export class DataContract extends BaseContract { * @return {Promise} resolved when done */ public async setMappingValue( - contract: any|string, - mappingName: string, - entryName: string, - value: any, - accountId: string, - dfsStorage = true, - encryptedHashes = true, - encryption: string = this.options.defaultCryptoAlgo, - encryptionContext = accountId): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('DataContractInterface', contract); + contract: any|string, + mappingName: string, + entryName: string, + value: any, + accountId: string, + dfsStorage = true, + encryptedHashes = true, + encryption: string = this.options.defaultCryptoAlgo, + encryptionContext = accountId, + ): Promise { + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('DataContractInterface', contract); let toSet; if (!dfsStorage) { // store as is toSet = value; } else { - const [ description, blockNr ] = await Promise.all([ + const [description, blockNr] = await Promise.all([ this.options.description.getDescriptionFromContract( - contract.options.address, encryptionContext), + contract.options.address, encryptionContext, + ), this.options.web3.eth.getBlockNumber(), ]); await this.validate(description, mappingName, value); const encrypted = await this.encrypt( - { private: value }, dataContract, accountId, mappingName, blockNr); + { private: value }, dataContract, accountId, mappingName, blockNr, encryption, + ); const stateMd5 = crypto.createHash('md5').update(encrypted).digest('hex'); toSet = await this.options.dfs.add(stateMd5, Buffer.from(encrypted)); } @@ -722,7 +800,7 @@ export class DataContract extends BaseContract { await this.options.executor.executeContractTransaction( dataContract, 'setMappingValue', - { from: accountId, autoGas: 1.1, }, + { from: accountId, autoGas: 1.1 }, this.options.web3.utils.sha3(mappingName), this.options.web3.utils.sha3(entryName), toSet, @@ -734,7 +812,7 @@ export class DataContract extends BaseContract { if (!description) { return true; } - const merged = {...description.public, ...description.private}; + const merged = { ...description.public, ...description.private }; if (merged.dataSchema && merged.dataSchema[fieldName]) { // check values if description found let schema = merged.dataSchema[fieldName]; @@ -750,12 +828,12 @@ export class DataContract extends BaseContract { values = [toCheck]; } const checkFails = values - .map(value => validator.validate(value)) - .filter(result => result !== true) - ; + .map((value) => validator.validate(value)) + .filter((result) => result !== true); if (checkFails.length) { throw new Error(`validation of input values failed with: ${JSON.stringify(checkFails)}`); } } + return true; } } diff --git a/src/contracts/digital-twin/container.spec.ts b/src/contracts/digital-twin/container.spec.ts index 74d29bea..5fdf85c2 100644 --- a/src/contracts/digital-twin/container.spec.ts +++ b/src/contracts/digital-twin/container.spec.ts @@ -17,12 +17,11 @@ the following URL: https://evan.network/license/ */ -import { promisify } from 'util'; -import { readFile } from 'fs'; - import 'mocha'; import * as chaiAsPromised from 'chai-as-promised'; import { expect, use } from 'chai'; +import { promisify } from 'util'; +import { readFile } from 'fs'; import { Executor, Ipfs, @@ -42,9 +41,9 @@ import { use(chaiAsPromised); -describe('Container', function() { +describe('Container', function test() { this.timeout(600000); - let [ owner, consumer, otherUser ] = accounts; + const [owner, consumer, otherUser] = accounts; let createRuntime: Function; let dfs: Ipfs; let defaultConfig: ContainerConfig; @@ -56,7 +55,7 @@ describe('Container', function() { version: '0.1.0', dbcpVersion: 2, }; - let runtimes: { [id: string]: ContainerOptions } = {}; + const runtimes: { [id: string]: ContainerOptions } = {}; let sha3: Function; /** @@ -72,7 +71,8 @@ describe('Container', function() { } /** - * Create a test container with the configured test accounts, adds applied properties and sets random values + * Create a test container with the configured test accounts + * adds applied properties and sets random values * * @param {string} properties The properties */ @@ -81,7 +81,7 @@ describe('Container', function() { const randomValues = { }; // setup custom properties - properties.forEach(property => { + properties.forEach((property) => { plugin.template.properties[property] = { dataSchema: { type: 'string' }, permissions: { 0: ['set'] }, @@ -99,7 +99,7 @@ describe('Container', function() { expect(await container.getEntry(property)).to.eq(randomValues[property]); })); - return { container, randomValues, }; + return { container, randomValues }; } before(async () => { @@ -110,7 +110,10 @@ describe('Container', function() { const sha9 = (accountId1, accountId2) => sha3(...[sha3(accountId1), sha3(accountId2)].sort()); createRuntime = async (accountId) => { // data contract instance has sha3 self key and edges to self and other accounts - const requestedKeys = [sha3(accountId), ...accounts.map(partner => sha9(accountId, partner))]; + const requestedKeys = [ + sha3(accountId), + ...accounts.map((partner) => sha9(accountId, partner)), + ]; const runtime = { contractLoader: await TestUtils.getContractLoader(web3), cryptoProvider: await TestUtils.getCryptoProvider(dfs), @@ -126,7 +129,7 @@ describe('Container', function() { runtime.executor.eventHub = await TestUtils.getEventHub(web3); return runtime; }; - for (let accountId of accounts) { + for (const accountId of accounts) { runtimes[accountId] = await createRuntime(accountId); } defaultConfig = { @@ -136,9 +139,9 @@ describe('Container', function() { }; // create factory for test const factory = await executor.createContract( - 'ContainerDataContractFactory', [], { from: accounts[0], gas: 6e6 }); + 'ContainerDataContractFactory', [], { from: accounts[0], gas: 6e6 }, + ); defaultConfig.factoryAddress = factory.options.address; - console.log(`Container tests are using factory "${defaultConfig.factoryAddress}"`); }); describe('when setting entries', async () => { @@ -157,9 +160,9 @@ describe('Container', function() { accountId: consumer, description, plugin: 'metadata', - factoryAddress: defaultConfig.factoryAddress - }) - ]) + factoryAddress: defaultConfig.factoryAddress, + }), + ]); const desc1 = await container1.getDescription(); const desc2 = await container2.getDescription(); @@ -169,9 +172,9 @@ describe('Container', function() { expect(desc1.identity).to.not.eq(desc2.identity); expect(desc2.identity).to.not.eq(desc3.identity); - expect(desc1.identity).to.not.eq(desc3.identity) - expect(desc4.identity).to.not.eq(desc3.identity) - expect(desc5.identity).to.not.eq(desc3.identity) + expect(desc1.identity).to.not.eq(desc3.identity); + expect(desc4.identity).to.not.eq(desc3.identity); + expect(desc5.identity).to.not.eq(desc3.identity); expect(await container1.getContractAddress()).to.match(/0x[0-9a-f]{40}/i); expect(await container2.getContractAddress()).to.match(/0x[0-9a-f]{40}/i); expect(await container3.getContractAddress()).to.match(/0x[0-9a-f]{40}/i); @@ -180,21 +183,22 @@ describe('Container', function() { const verifications: ContainerVerificationEntry[] = [...Array(3)].map( - (_, i) => ( { topic: `verifcation_${i}` })); + (_, i) => ({ topic: `verifcation_${i}` } as ContainerVerificationEntry), + ); - for (let container of [container1, container2, container3]) { + for (const container of [container1, container2, container3]) { await container.addVerifications(verifications); const verificationsResults = await container.getVerifications(); expect(verificationsResults.length).to.eq(3); // all verification lists should have at least 1 valid verification - const allValid = verificationsResults.every(vs => vs.some(v => v.valid)); + const allValid = verificationsResults.every((vs) => vs.some((v) => v.valid)); expect(allValid).to.be.true; // all verification should be confirmed, as issuing account is owner const allConfirmed = verificationsResults.every( - vs => vs.some(v => v.status === VerificationsStatus.Confirmed)); + (vs) => vs.some((v) => v.status === VerificationsStatus.Confirmed), + ); expect(allConfirmed).to.be.true; } - }); it('can get the correct owner for contracts', async () => { @@ -214,13 +218,13 @@ describe('Container', function() { type: 'entry', }, }, - } + }, }; const container = await Container.create(runtimes[owner], { ...defaultConfig, plugin }); expect(await container.getEntry('type')).to.eq(plugin.template.type); }); - it('can add new entry properties', async() => { + it('can add new entry properties', async () => { const container = await Container.create(runtimes[owner], defaultConfig); await container.ensureProperty('testField', Container.defaultSchemas.stringEntry); await container.shareProperties([{ accountId: consumer, readWrite: ['testField'] }]); @@ -234,7 +238,7 @@ describe('Container', function() { }); it('can set and get entries for properties defined in (custom) plugin', async () => { - const { container, randomValues } = await createTestContainerWithProperties([ 'testField' ]); + await expect(createTestContainerWithProperties(['testField'])).to.be.fulfilled; }); it('can set entries if not defined in plugin template (auto adds properties)', async () => { @@ -256,20 +260,21 @@ describe('Container', function() { await container.ensureProperty('sampleFiles', Container.defaultSchemas.filesEntry); const file = await promisify(readFile)( - `${__dirname}/testfiles/animal-animal-photography-cat-96938.jpg`); + `${__dirname}/testfiles/animal-animal-photography-cat-96938.jpg`, + ); const sampleFiles = { files: [{ name: 'animal-animal-photography-cat-96938.jpg', fileType: 'image/jpeg', file, - }] + }], }; const sampleFilesBackup = { files: [{ name: 'animal-animal-photography-cat-96938.jpg', fileType: 'image/jpeg', file, - }] + }], }; await container.setEntry('sampleFiles', sampleFiles); @@ -283,7 +288,7 @@ describe('Container', function() { type: 'object', properties: { description: Container.defaultSchemas.stringEntry, - images: Container.defaultSchemas.filesEntry + images: Container.defaultSchemas.filesEntry, }, }, permissions: { 0: ['set'] }, @@ -292,7 +297,8 @@ describe('Container', function() { const container = await Container.create(runtimes[owner], { ...defaultConfig, plugin }); const file = await promisify(readFile)( - `${__dirname}/testfiles/animal-animal-photography-cat-96938.jpg`); + `${__dirname}/testfiles/animal-animal-photography-cat-96938.jpg`, + ); const sampleValue = { description: 'what a cute kitten', images: { @@ -302,7 +308,7 @@ describe('Container', function() { file, }], }, - } + }; const sampleValueBackup = { description: 'what a cute kitten', images: { @@ -336,19 +342,19 @@ describe('Container', function() { it('can set list entries if not defined in template (auto adds properties)', async () => { const container = await Container.create(runtimes[owner], defaultConfig); const randomString = Math.floor(Math.random() * 1e12).toString(36); - await container.addListEntries('testList', [ randomString ]); + await container.addListEntries('testList', [randomString]); - expect(await container.getListEntries('testList')).to.deep.eq([ randomString ]); + expect(await container.getListEntries('testList')).to.deep.eq([randomString]); const expectedSchema = { $id: 'testList_schema', type: 'array', - items: { type: 'string'}, + items: { type: 'string' }, }; const containerDescription = await container.getDescription(); expect(containerDescription.dataSchema.testList).to.deep.eq(expectedSchema); }); - it('can add new list properties', async() => { + it('can add new list properties', async () => { const container = await Container.create(runtimes[owner], defaultConfig); await container.ensureProperty('testList', Container.defaultSchemas.stringList); await container.shareProperties([{ accountId: consumer, readWrite: ['testList'] }]); @@ -366,9 +372,11 @@ describe('Container', function() { await container.ensureProperty('testList', Container.defaultSchemas.filesList); const file1 = await promisify(readFile)( - `${__dirname}/testfiles/animal-animal-photography-cat-96938.jpg`); + `${__dirname}/testfiles/animal-animal-photography-cat-96938.jpg`, + ); const file2 = await promisify(readFile)( - `${__dirname}/testfiles/adorable-animal-animal-photography-774731.jpg`); + `${__dirname}/testfiles/adorable-animal-animal-photography-774731.jpg`, + ); const sampleFiles = [ { files: [{ @@ -383,7 +391,7 @@ describe('Container', function() { fileType: 'image/jpeg', file: file2, }], - } + }, ]; const sampleFilesBackup = [ { @@ -399,7 +407,7 @@ describe('Container', function() { fileType: 'image/jpeg', file: file2, }], - } + }, ]; await container.addListEntries('testList', sampleFiles); @@ -415,7 +423,7 @@ describe('Container', function() { type: 'object', properties: { description: Container.defaultSchemas.stringEntry, - images: Container.defaultSchemas.filesEntry + images: Container.defaultSchemas.filesEntry, }, }, }, @@ -425,9 +433,11 @@ describe('Container', function() { const container = await Container.create(runtimes[owner], { ...defaultConfig, plugin }); const file1 = await promisify(readFile)( - `${__dirname}/testfiles/animal-animal-photography-cat-96938.jpg`); + `${__dirname}/testfiles/animal-animal-photography-cat-96938.jpg`, + ); const file2 = await promisify(readFile)( - `${__dirname}/testfiles/adorable-animal-animal-photography-774731.jpg`); + `${__dirname}/testfiles/adorable-animal-animal-photography-774731.jpg`, + ); const sampleFiles = [ { description: 'what a cute kitten', @@ -515,10 +525,15 @@ describe('Container', function() { }); it('can clone contracts', async () => { - const { container, randomValues } = await createTestContainerWithProperties([ 'testField' ]); + const { container, randomValues } = await createTestContainerWithProperties(['testField']); - const clonedContainer = await Container.clone(runtimes[owner], defaultConfig, container, true); - expect(await clonedContainer.getEntry('testField')).to.eq(randomValues['testField']); + const clonedContainer = await Container.clone( + runtimes[owner], + defaultConfig, + container, + true, + ); + expect(await clonedContainer.getEntry('testField')).to.eq((randomValues as any).testField); }); it('can save plugins to users profile', async () => { @@ -636,7 +651,7 @@ describe('Container', function() { describe('when sharing properties', async () => { describe('when sharing entries', async () => { - it('can share read access a property from owner to another user', async() => { + it('can share read access a property from owner to another user', async () => { const plugin: ContainerPlugin = JSON.parse(JSON.stringify(Container.plugins.metadata)); plugin.template.properties.testField = { dataSchema: { type: 'string' }, @@ -660,7 +675,7 @@ describe('Container', function() { expect(shareConfig.read).to.include('testField'); }); - it('can share write access a property from owner to another user', async() => { + it('can share write access a property from owner to another user', async () => { const plugin: ContainerPlugin = JSON.parse(JSON.stringify(Container.plugins.metadata)); plugin.template.properties.testField = { dataSchema: { type: 'string' }, @@ -692,7 +707,7 @@ describe('Container', function() { }); describe('when sharing lists', async () => { - it('can share read access a property from owner to another user', async() => { + it('can share read access a property from owner to another user', async () => { const plugin: ContainerPlugin = JSON.parse(JSON.stringify(Container.plugins.metadata)); plugin.template.properties.testList = { dataSchema: { type: 'array', items: { type: 'string' } }, @@ -712,7 +727,7 @@ describe('Container', function() { expect(await consumerContainer.getListEntries('testList')).to.deep.eq([randomString]); }); - it('can share write access a property from owner to another user', async() => { + it('can share write access a property from owner to another user', async () => { const plugin: ContainerPlugin = JSON.parse(JSON.stringify(Container.plugins.metadata)); plugin.template.properties.testList = { dataSchema: { type: 'array', items: { type: 'string' } }, @@ -741,7 +756,7 @@ describe('Container', function() { .to.deep.eq([randomString, newRandomString]); }); - it('can share remove access a property from owner to another user', async() => { + it('can share remove access a property from owner to another user', async () => { const plugin: ContainerPlugin = JSON.parse(JSON.stringify(Container.plugins.metadata)); plugin.template.properties.testList = { dataSchema: { type: 'array', items: { type: 'string' } }, @@ -775,7 +790,7 @@ describe('Container', function() { }); describe('when working on shared containers', async () => { - it('cannot have other user access properties before sharing them', async() => { + it('cannot have other user access properties before sharing them', async () => { const plugin: ContainerPlugin = JSON.parse(JSON.stringify(Container.plugins.metadata)); plugin.template.properties.testField = { dataSchema: { type: 'string' }, @@ -816,13 +831,15 @@ describe('Container', function() { expect(await consumerContainer.getEntry('testField')).to.eq(randomString); const sharePromise = consumerContainer.shareProperties( - [{ accountId: otherUser, read: ['testField'] }]); + [{ accountId: otherUser, read: ['testField'] }], + ); await expect(sharePromise).to.be.rejectedWith(new RegExp( - '^current account "0x[0-9a-f]{40}" is unable to share properties, as it isn\'t owner of ' + - 'the underlying contract "0x[0-9a-f]{40}"$', 'i')); + '^current account "0x[0-9a-f]{40}" is unable to share properties, as it isn\'t owner of ' + + 'the underlying contract "0x[0-9a-f]{40}"$', 'i', + )); }); - it('cannot share access from a non-member account to another user', async() => { + it('cannot share access from a non-member account to another user', async () => { const plugin: ContainerPlugin = JSON.parse(JSON.stringify(Container.plugins.metadata)); plugin.template.properties.testField = { dataSchema: { type: 'string' }, @@ -841,13 +858,15 @@ describe('Container', function() { ); const sharePromise = consumerContainer.shareProperties( - [{ accountId: consumer, read: ['testField'] }]); + [{ accountId: consumer, read: ['testField'] }], + ); await expect(sharePromise).to.be.rejectedWith( - new RegExp('^current account "0x[0-9a-f]{40}" is unable to share properties, as it ' + - 'isn\'t owner of the underlying contract "0x[0-9a-f]{40}"$', 'i')); + new RegExp('^current account "0x[0-9a-f]{40}" is unable to share properties, as it ' + + 'isn\'t owner of the underlying contract "0x[0-9a-f]{40}"$', 'i'), + ); }); - it('can clone a partially shared container from the receiver of a sharing', async() => { + it('can clone a partially shared container from the receiver of a sharing', async () => { const plugin: ContainerPlugin = JSON.parse(JSON.stringify(Container.plugins.metadata)); plugin.template.properties.testField = { dataSchema: { type: 'string' }, @@ -867,7 +886,8 @@ describe('Container', function() { expect(await consumerContainer.getEntry('testField')).to.eq(randomString); const clonedContainer = await Container.clone( - runtimes[consumer], { ...defaultConfig, accountId: consumer }, consumerContainer, true); + runtimes[consumer], { ...defaultConfig, accountId: consumer }, consumerContainer, true, + ); expect(await clonedContainer.getEntry('testField')).to.eq(randomString); }); }); @@ -875,12 +895,12 @@ describe('Container', function() { describe('when unsharing properties', async () => { // positive unshare - it('can revoke read access of one property from owner to another user', async() => { - const { container, randomValues } = await createTestContainerWithProperties([ 'testField' ]); + it('can revoke read access of one property from owner to another user', async () => { + const { container, randomValues } = await createTestContainerWithProperties(['testField']); await container.shareProperties([{ accountId: consumer, read: ['testField'] }]); let consumerContainer = await getConsumerContainer(container); - expect(await consumerContainer.getEntry('testField')).to.eq(randomValues['testField']); + expect(await consumerContainer.getEntry('testField')).to.eq((randomValues as any).testField); let shareConfig = await container.getContainerShareConfigForAccount(consumer); expect(shareConfig).not.to.haveOwnProperty('readWrite'); @@ -906,18 +926,20 @@ describe('Container', function() { expect(shareConfig).not.to.haveOwnProperty('readWrite'); const contractSharings = await consumerSharing .getSharingsFromContract(newConsumerRuntime.contractLoader.loadContract( - 'DataContract', await container.getContractAddress())); + 'DataContract', await container.getContractAddress(), + )); expect(contractSharings).not.to.haveOwnProperty(sha3(consumer)); }); - it('can revoke read access of multiple properties from owner to another user', async() => { - const { container, randomValues } = await createTestContainerWithProperties([ 'testField', 'anotherTestField' ]); + it('can revoke read access of multiple properties from owner to another user', async () => { + const { container, randomValues } = await createTestContainerWithProperties(['testField', 'anotherTestField']); await container.shareProperties( - [{ accountId: consumer, read: ['testField', 'anotherTestField'] }]); + [{ accountId: consumer, read: ['testField', 'anotherTestField'] }], + ); let consumerContainer = await getConsumerContainer(container); - expect(await consumerContainer.getEntry('testField')).to.eq(randomValues['testField']); - expect(await consumerContainer.getEntry('anotherTestField')).to.eq(randomValues['anotherTestField']); + expect(await consumerContainer.getEntry('testField')).to.eq((randomValues as any).testField); + expect(await consumerContainer.getEntry('anotherTestField')).to.eq((randomValues as any).anotherTestField); let shareConfig = await container.getContainerShareConfigForAccount(consumer); expect(shareConfig).not.to.haveOwnProperty('readWrite'); @@ -944,7 +966,8 @@ describe('Container', function() { expect(shareConfig).not.to.haveOwnProperty('readWrite'); let contractSharings = await ((consumerContainer as any).options as ContainerOptions) .sharing.getSharingsFromContract(newConsumerRuntime.contractLoader.loadContract( - 'DataContract', await container.getContractAddress())); + 'DataContract', await container.getContractAddress(), + )); expect(contractSharings).to.haveOwnProperty(sha3(consumer)); expect(contractSharings[sha3(consumer)]).to.haveOwnProperty(sha3('anotherTestField')); expect(contractSharings[sha3(consumer)]).to.haveOwnProperty(sha3('*')); @@ -967,16 +990,17 @@ describe('Container', function() { expect(shareConfig).not.to.haveOwnProperty('readWrite'); contractSharings = await consumerSharing .getSharingsFromContract(newConsumerRuntime.contractLoader.loadContract( - 'DataContract', await container.getContractAddress())); + 'DataContract', await container.getContractAddress(), + )); expect(contractSharings).not.to.haveOwnProperty(sha3(consumer)); }); - it('can revoke readWrite access of one property from owner to another user', async() => { - const { container, randomValues } = await createTestContainerWithProperties([ 'testField', ]); + it('can revoke readWrite access of one property from owner to another user', async () => { + const { container, randomValues } = await createTestContainerWithProperties(['testField']); await container.shareProperties([{ accountId: consumer, readWrite: ['testField'] }]); let consumerContainer = await getConsumerContainer(container); - expect(await consumerContainer.getEntry('testField')).to.eq(randomValues['testField']); + expect(await consumerContainer.getEntry('testField')).to.eq((randomValues as any).testField); let shareConfig = await container.getContainerShareConfigForAccount(consumer); expect(shareConfig).not.to.haveOwnProperty('read'); @@ -1002,18 +1026,20 @@ describe('Container', function() { expect(shareConfig).not.to.haveOwnProperty('readWrite'); const contractSharings = await consumerSharing .getSharingsFromContract(newConsumerRuntime.contractLoader.loadContract( - 'DataContract', await container.getContractAddress())); + 'DataContract', await container.getContractAddress(), + )); expect(contractSharings).not.to.haveOwnProperty(sha3(consumer)); }); - it('can revoke readWrite access of multiple properties from owner to another user', async() => { - const { container, randomValues } = await createTestContainerWithProperties([ 'testField', 'anotherTestField' ]); + it('can revoke readWrite access of multiple properties from owner to another user', async () => { + const { container, randomValues } = await createTestContainerWithProperties(['testField', 'anotherTestField']); await container.shareProperties( - [{ accountId: consumer, readWrite: ['testField', 'anotherTestField'] }]); + [{ accountId: consumer, readWrite: ['testField', 'anotherTestField'] }], + ); let consumerContainer = await getConsumerContainer(container); - expect(await consumerContainer.getEntry('testField')).to.eq(randomValues['testField']); - expect(await consumerContainer.getEntry('anotherTestField')).to.eq(randomValues['anotherTestField']); + expect(await consumerContainer.getEntry('testField')).to.eq((randomValues as any).testField); + expect(await consumerContainer.getEntry('anotherTestField')).to.eq((randomValues as any).anotherTestField); let shareConfig = await container.getContainerShareConfigForAccount(consumer); expect(shareConfig).not.to.haveOwnProperty('read'); @@ -1040,7 +1066,8 @@ describe('Container', function() { expect(shareConfig.readWrite).to.include('anotherTestField'); let contractSharings = await ((consumerContainer as any).options as ContainerOptions) .sharing.getSharingsFromContract(newConsumerRuntime.contractLoader.loadContract( - 'DataContract', await container.getContractAddress())); + 'DataContract', await container.getContractAddress(), + )); expect(contractSharings).to.haveOwnProperty(sha3(consumer)); expect(contractSharings[sha3(consumer)]).to.haveOwnProperty(sha3('anotherTestField')); expect(contractSharings[sha3(consumer)]).to.haveOwnProperty(sha3('*')); @@ -1063,16 +1090,17 @@ describe('Container', function() { expect(shareConfig).not.to.haveOwnProperty('readWrite'); contractSharings = await consumerSharing .getSharingsFromContract(newConsumerRuntime.contractLoader.loadContract( - 'DataContract', await container.getContractAddress())); + 'DataContract', await container.getContractAddress(), + )); expect(contractSharings).not.to.haveOwnProperty(sha3(consumer)); }); - it('can remove write permissions but keep read permissions', async() => { - const { container, randomValues } = await createTestContainerWithProperties([ 'testField', ]); + it('can remove write permissions but keep read permissions', async () => { + const { container, randomValues } = await createTestContainerWithProperties(['testField']); await container.shareProperties([{ accountId: consumer, readWrite: ['testField'] }]); let consumerContainer = await getConsumerContainer(container); - expect(await consumerContainer.getEntry('testField')).to.eq(randomValues['testField']); + expect(await consumerContainer.getEntry('testField')).to.eq((randomValues as any).testField); let shareConfig = await container.getContainerShareConfigForAccount(consumer); expect(shareConfig).not.to.haveOwnProperty('read'); @@ -1093,36 +1121,36 @@ describe('Container', function() { consumerSharing = ((consumerContainer as any).options as ContainerOptions).sharing; shareConfig = await container.getContainerShareConfigForAccount(consumer); expect(await consumerContainer.getEntry('testField')) - .to.eq(randomValues['testField']); + .to.eq((randomValues as any).testField); expect(shareConfig).to.haveOwnProperty('read'); expect(shareConfig).not.to.haveOwnProperty('readWrite'); const contractSharings = await consumerSharing .getSharingsFromContract(newConsumerRuntime.contractLoader.loadContract( - 'DataContract', await container.getContractAddress())); + 'DataContract', await container.getContractAddress(), + )); expect(contractSharings).to.haveOwnProperty(sha3(consumer)); }); // negative unshare tests - it('cannot revoke access from another user to another user', async() => { - const { container, randomValues } = await createTestContainerWithProperties([ 'testField', 'anotherTestField' ]); + it('cannot revoke access from another user to another user', async () => { + const { container } = await createTestContainerWithProperties(['testField', 'anotherTestField']); await container.shareProperties([{ accountId: consumer, readWrite: ['testField'] }]); // now unshare - let consumerContainer = await getConsumerContainer(container); - const unshare = consumerContainer.unshareProperties([{ - accountId: consumer, readWrite: ['testField'] }]) - await expect(unshare).to.be.rejectedWith(new RegExp(`^current account "${ consumer }" is unable to unshare properties, as it isn't owner of the underlying contract`, 'i')); + const consumerContainer = await getConsumerContainer(container); + const unshare = consumerContainer.unshareProperties([{ accountId: consumer, readWrite: ['testField'] }]); + await expect(unshare).to.be.rejectedWith(new RegExp(`^current account "${consumer}" is unable to unshare properties, as it isn't owner of the underlying contract`, 'i')); }); // // setContainerShareConfigs - it('can save a full share configuration for a user', async() => { - const { container, } = await createTestContainerWithProperties([ 'testField', 'testField2', 'testField3' ]); + it('can save a full share configuration for a user', async () => { + const { container } = await createTestContainerWithProperties(['testField', 'testField2', 'testField3']); await container.shareProperties([{ accountId: consumer, - read: [ 'testField', ], - readWrite: [ 'testField2', ] + read: ['testField'], + readWrite: ['testField2'], }]); let shareConfig = await container.getContainerShareConfigForAccount(consumer); @@ -1132,7 +1160,7 @@ describe('Container', function() { expect(shareConfig.read).to.not.include('testField3'); expect(shareConfig.readWrite).to.not.include('testField3'); - shareConfig.readWrite = [ 'testField3' ]; + shareConfig.readWrite = ['testField3']; await container.setContainerShareConfigs(shareConfig); shareConfig = await container.getContainerShareConfigForAccount(consumer); @@ -1142,19 +1170,19 @@ describe('Container', function() { expect(shareConfig.readWrite).to.include('testField3'); }); - it('can save share configurations for multiple users', async() => { - const { container, } = await createTestContainerWithProperties([ 'testField', 'testField2', 'testField3' ]); + it('can save share configurations for multiple users', async () => { + const { container } = await createTestContainerWithProperties(['testField', 'testField2', 'testField3']); await container.shareProperties([{ accountId: consumer, - read: [ 'testField', ], - readWrite: [ 'testField2', ] + read: ['testField'], + readWrite: ['testField2'], }]); await container.shareProperties([{ accountId: otherUser, - read: [ ], - readWrite: [ 'testField3', ] + read: [], + readWrite: ['testField3'], }]); let shareConfig = await container.getContainerShareConfigForAccount(consumer); @@ -1167,9 +1195,9 @@ describe('Container', function() { expect(otherShareConfig.read).to.be.eq(undefined); expect(otherShareConfig.readWrite).to.include('testField3'); - shareConfig.readWrite = [ 'testField3' ]; - otherShareConfig.readWrite = [ 'testField2' ]; - await container.setContainerShareConfigs([ shareConfig, otherShareConfig ]); + shareConfig.readWrite = ['testField3']; + otherShareConfig.readWrite = ['testField2']; + await container.setContainerShareConfigs([shareConfig, otherShareConfig]); shareConfig = await container.getContainerShareConfigForAccount(consumer); expect(shareConfig.read).to.include('testField'); @@ -1184,41 +1212,41 @@ describe('Container', function() { expect(otherShareConfig.readWrite).to.not.include('testField3'); }); - it('cannot save share configurations as another user', async() => { - const { container, } = await createTestContainerWithProperties([ 'testField', 'testField2', 'testField3' ]); + it('cannot save share configurations as another user', async () => { + const { container } = await createTestContainerWithProperties(['testField', 'testField2', 'testField3']); await container.shareProperties([{ accountId: consumer, - read: [ 'testField', ], - readWrite: [ 'testField2', ] + read: ['testField'], + readWrite: ['testField2'], }]); - let shareConfig = await container.getContainerShareConfigForAccount(consumer); + const shareConfig = await container.getContainerShareConfigForAccount(consumer); const consumerContainer = await getConsumerContainer(container); - shareConfig.readWrite = [ 'testField3' ]; + shareConfig.readWrite = ['testField3']; const sharingProcess = consumerContainer.setContainerShareConfigs(shareConfig); - await expect(sharingProcess).to.be.rejectedWith(new RegExp(`^current account "${ consumer }" is unable to share properties, as it isn't owner of the underlying contract`, 'i')); + await expect(sharingProcess).to.be.rejectedWith(new RegExp(`^current account "${consumer}" is unable to share properties, as it isn't owner of the underlying contract`, 'i')); }); - it('can apply the original sharing configuration by saving a full share configuration for a user', async() => { - const { container, } = await createTestContainerWithProperties([ 'testField', 'testField2', 'testField3' ]); + it('can apply the original sharing configuration by saving a full share configuration for a user', async () => { + const { container } = await createTestContainerWithProperties(['testField', 'testField2', 'testField3']); await container.shareProperties([{ accountId: consumer, - read: [ 'testField', ], - readWrite: [ 'testField2', ] + read: ['testField'], + readWrite: ['testField2'], }]); let shareConfig = await container.getContainerShareConfigForAccount(consumer); - let originalConfig = JSON.parse(JSON.stringify(shareConfig)); + const originalConfig = JSON.parse(JSON.stringify(shareConfig)); expect(shareConfig.read).to.include('testField'); expect(shareConfig.readWrite).to.include('testField2'); expect(shareConfig.read).to.not.include('testField3'); expect(shareConfig.readWrite).to.not.include('testField3'); - shareConfig.readWrite = [ 'testField3' ]; + shareConfig.readWrite = ['testField3']; await container.setContainerShareConfigs(shareConfig, originalConfig); shareConfig = await container.getContainerShareConfigForAccount(consumer); @@ -1228,23 +1256,23 @@ describe('Container', function() { expect(shareConfig.readWrite).to.include('testField3'); }); - it('can apply different original sharing configurations by saving a full share configuration for a user and the API will reload original ones', async() => { - const { container, } = await createTestContainerWithProperties([ 'testField', 'testField2', 'testField3' ]); + it('can apply different original sharing configurations by saving a full share configuration for a user and the API will reload original ones', async () => { + const { container } = await createTestContainerWithProperties(['testField', 'testField2', 'testField3']); await container.shareProperties([{ accountId: consumer, - read: [ 'testField', ], - readWrite: [ 'testField2', ] + read: ['testField'], + readWrite: ['testField2'], }]); await container.shareProperties([{ accountId: otherUser, - read: [ ], - readWrite: [ 'testField3', ] + read: [], + readWrite: ['testField3'], }]); let shareConfig = await container.getContainerShareConfigForAccount(consumer); - let originalConfig = JSON.parse(JSON.stringify(shareConfig)); + const originalConfig = JSON.parse(JSON.stringify(shareConfig)); expect(shareConfig.read).to.include('testField'); expect(shareConfig.readWrite).to.include('testField2'); expect(shareConfig.read).to.not.include('testField3'); @@ -1254,9 +1282,9 @@ describe('Container', function() { expect(otherShareConfig.read).to.be.eq(undefined); expect(otherShareConfig.readWrite).to.include('testField3'); - shareConfig.readWrite = [ 'testField3' ]; - otherShareConfig.readWrite = [ 'testField2' ]; - await container.setContainerShareConfigs([ shareConfig, otherShareConfig ], [ originalConfig ]); + shareConfig.readWrite = ['testField3']; + otherShareConfig.readWrite = ['testField2']; + await container.setContainerShareConfigs([shareConfig, otherShareConfig], [originalConfig]); shareConfig = await container.getContainerShareConfigForAccount(consumer); expect(shareConfig.read).to.include('testField'); @@ -1272,7 +1300,7 @@ describe('Container', function() { }); // cleanup properties - it('automatically removes field from description when last member of group is removed (1 member)', async() => { + it('automatically removes field from description when last member of group is removed (1 member)', async () => { const plugin: ContainerPlugin = JSON.parse(JSON.stringify(Container.plugins.metadata)); plugin.template.properties.testField = { dataSchema: { type: 'string' }, @@ -1286,7 +1314,7 @@ describe('Container', function() { expect(containerDescription.dataSchema).not.to.haveOwnProperty('testField'); }); - it('automatically removes field from description when last member of group is removed (2 members)', async() => { + it('automatically removes field from description when last member of group is removed (2 members)', async () => { const plugin: ContainerPlugin = JSON.parse(JSON.stringify(Container.plugins.metadata)); plugin.template.properties.testField = { dataSchema: { type: 'string' }, @@ -1305,7 +1333,7 @@ describe('Container', function() { expect(containerDescription.dataSchema).not.to.haveOwnProperty('testField'); }); - it('does not allow owner removal without setting the force flag', async() => { + it('does not allow owner removal without setting the force flag', async () => { const plugin: ContainerPlugin = JSON.parse(JSON.stringify(Container.plugins.metadata)); plugin.template.properties.testField = { dataSchema: { type: 'string' }, @@ -1313,59 +1341,62 @@ describe('Container', function() { type: 'entry', }; const container = await Container.create(runtimes[owner], { ...defaultConfig, plugin }); - const unshare = container.unshareProperties([{ accountId: owner, readWrite: ['testField'], }]); - await expect(unshare).to.be.rejectedWith(new RegExp(`^current account "${ owner }" is owner of the contract and cannot remove himself from sharing without force attribute`, 'i')); + const unshare = container.unshareProperties([{ accountId: owner, readWrite: ['testField'] }]); + await expect(unshare).to.be.rejectedWith(new RegExp(`^current account "${owner}" is owner of the contract and cannot remove himself from sharing without force attribute`, 'i')); }); - it('can remove properties as owner', async() => { - const { container, } = await createTestContainerWithProperties([ 'testField', 'testField2', 'testField3' ]); + + it('can remove properties as owner', async () => { + const { container } = await createTestContainerWithProperties(['testField', 'testField2', 'testField3']); await container.shareProperties([{ accountId: consumer, - read: [ 'testField', ], - readWrite: [ 'testField2', ] + read: ['testField'], + readWrite: ['testField2'], }]); await container.removeEntries('testField'); - const description = await container.getDescription(); - expect(description.dataSchema).not.to.haveOwnProperty('testField'); + const currentDescription = await container.getDescription(); + expect(currentDescription.dataSchema).not.to.haveOwnProperty('testField'); const shareConfig = await container.getContainerShareConfigForAccount(consumer); expect(shareConfig.read).to.be.eq(undefined); }); - it('can remove multiple properties as owner', async() => { - const { container, } = await createTestContainerWithProperties([ 'testField', 'testField2', 'testField3' ]); + + it('can remove multiple properties as owner', async () => { + const { container } = await createTestContainerWithProperties(['testField', 'testField2', 'testField3']); await container.shareProperties([{ accountId: consumer, - read: [ 'testField', ], - readWrite: [ 'testField2', ] + read: ['testField'], + readWrite: ['testField2'], }]); - await container.removeEntries([ 'testField', 'testField2' ]); + await container.removeEntries(['testField', 'testField2']); - const description = await container.getDescription(); - expect(description.dataSchema).not.to.haveOwnProperty('testField'); - expect(description.dataSchema).not.to.haveOwnProperty('testField2'); + const currentDescription = await container.getDescription(); + expect(currentDescription.dataSchema).not.to.haveOwnProperty('testField'); + expect(currentDescription.dataSchema).not.to.haveOwnProperty('testField2'); const shareConfig = await container.getContainerShareConfigForAccount(consumer); expect(shareConfig.read).to.be.eq(undefined); expect(shareConfig.readWrite).to.be.eq(undefined); }); - it('cannot remove properties as another user', async() => { - const { container, } = await createTestContainerWithProperties([ 'testField', 'testField2', 'testField3' ]); + + it('cannot remove properties as another user', async () => { + const { container } = await createTestContainerWithProperties(['testField', 'testField2', 'testField3']); await container.shareProperties([{ accountId: consumer, - read: [ 'testField', ], - readWrite: [ 'testField2', ] + read: ['testField'], + readWrite: ['testField2'], }]); const consumerContainer = await getConsumerContainer(container); await expect(consumerContainer.removeEntries('testField')).to.be.rejectedWith( - new RegExp(`^current account "${ consumer }" is unable to unshare properties,` + - ` as it isn\'t owner of the underlying contract ` + - `${ await container.getContractAddress() }`, 'i') + new RegExp(`^current account "${consumer}" is unable to unshare properties,` + + ' as it isn\'t owner of the underlying contract ' + + `${await container.getContractAddress()}`, 'i'), ); }); }); @@ -1374,34 +1405,36 @@ describe('Container', function() { it('can set verifications to container', async () => { const container = await Container.create(runtimes[owner], defaultConfig); const verifications: ContainerVerificationEntry[] = [...Array(3)].map( - (_, i) => ({ topic: `verifcation_${i}` })); + (_, i) => ({ topic: `verifcation_${i}` }), + ); await container.addVerifications(verifications); const verificationsResults = await container.getVerifications(); expect(verificationsResults.length).to.eq(3); // all verification lists should have at least 1 valid verification - const allValid = verificationsResults.every(vs => vs.some(v => v.valid)); + const allValid = verificationsResults.every((vs) => vs.some((v) => v.valid)); expect(allValid).to.be.true; // all verifications should be confirmed, as issuing account is owner const allConfirmed = verificationsResults.every( - vs => vs.some(v => v.status === VerificationsStatus.Confirmed)); + (vs) => vs.some((v) => v.status === VerificationsStatus.Confirmed), + ); expect(allConfirmed).to.be.true; }); }); - describe('when fetching permissions from container', async() => { - it('can fetch permissions for a single account', async() => { - const { container, randomValues } = await createTestContainerWithProperties([ 'testField' ]); + describe('when fetching permissions from container', async () => { + it('can fetch permissions for a single account', async () => { + const { container, randomValues } = await createTestContainerWithProperties(['testField']); await container.shareProperties([{ accountId: consumer, read: ['testField'] }]); const consumerContainer = await getConsumerContainer(container); - expect(await consumerContainer.getEntry('testField')).to.eq(randomValues['testField']); + expect(await consumerContainer.getEntry('testField')).to.eq((randomValues as any).testField); const shareConfig = await container.getContainerShareConfigForAccount(consumer); expect(shareConfig).to.haveOwnProperty('read'); expect(shareConfig.read).to.include('testField'); }); - it('can fetch permissions for all accounts', async() => { + it('can fetch permissions for all accounts', async () => { const container = await Container.create(runtimes[owner], defaultConfig); const randomString1 = Math.floor(Math.random() * 1e12).toString(36); await container.setEntry('testField1', randomString1); @@ -1421,7 +1454,7 @@ describe('Container', function() { { accountId: consumer, readWrite: ['testField1'], read: ['testField2'] }, ]; const shareConfigs = await container.getContainerShareConfigs(); - const byAccountId = (e1, e2) => { return e1.accountId < e2.accountId ? -1 : 1; }; + const byAccountId = (e1, e2) => (e1.accountId < e2.accountId ? -1 : 1); expect(shareConfigs.sort(byAccountId)).to.deep.eq(expected.sort(byAccountId)); }); }); diff --git a/src/contracts/digital-twin/container.ts b/src/contracts/digital-twin/container.ts index 6777ce02..4fd1348f 100644 --- a/src/contracts/digital-twin/container.ts +++ b/src/contracts/digital-twin/container.ts @@ -17,6 +17,9 @@ the following URL: https://evan.network/license/ */ +// disabled until global function handling is resolved +/* eslint-disable @typescript-eslint/no-use-before-define */ + import * as Throttle from 'promise-parallel-throttle'; import BigNumber from 'bignumber.js'; import { Mutex } from 'async-mutex'; @@ -110,7 +113,7 @@ export interface ContainerUnshareConfig { /** list of properties, for which write permissions should be removed */ write?: string[]; /** Without force flag, removal of the owner will throw an error. By setting - to true, force will even remove the owner **/ + to true, force will even remove the owner * */ force?: boolean; } @@ -118,9 +121,9 @@ export interface ContainerUnshareConfig { * base definition of a container instance, covers properties setup and permissions */ export interface ContainerPlugin { - /** container dbcp description (name, description, ...) **/ + /** container dbcp description (name, description, ...) * */ description?: any; - /** template for container instances, covers properties setup and permissions **/ + /** template for container instances, covers properties setup and permissions * */ template: ContainerTemplate; } @@ -182,6 +185,7 @@ export class Container extends Logger { version: '0.1.0', dbcpVersion: 2, }; + public static defaultSchemas = { booleanEntry: { type: 'boolean' }, booleanList: { type: 'array', items: { type: 'boolean' } }, @@ -191,10 +195,10 @@ export class Container extends Logger { properties: { additionalProperties: false, files: { - type: 'array', items: { type: 'string', } + type: 'array', items: { type: 'string' }, }, }, - required: [ 'files' ], + required: ['files'], }, filesList: { type: 'array', @@ -204,10 +208,10 @@ export class Container extends Logger { properties: { additionalProperties: false, files: { - type: 'array', items: { type: 'string', } + type: 'array', items: { type: 'string' }, }, }, - required: [ 'files' ], + required: ['files'], }, }, numberEntry: { type: 'number' }, @@ -217,8 +221,11 @@ export class Container extends Logger { stringEntry: { type: 'string' }, stringList: { type: 'array', items: { type: 'string' } }, }; + public static defaultPlugin = 'metadata'; + public static profilePluginsKey = 'templates.datacontainer.digitaltwin.evan'; + public static plugins: { [id: string]: ContainerPlugin } = { metadata: { description: { @@ -231,10 +238,15 @@ export class Container extends Logger { }, }, }; + private config: ContainerConfig; + private contract: any; + private mutexes: { [id: string]: Mutex }; + private options: ContainerOptions; + private reservedRoles = 64; /** @@ -273,34 +285,36 @@ export class Container extends Logger { 'IdentityHolder', contractIdentities, 'IdentityCreated', - ({ returnValues: { identity }}) => { + ({ returnValues: { identity } }) => { if (!pendingIdentities.includes(identity)) { pendingIdentities.push(identity); return true; - } else { - return false; } + return false; }, - () => {} + () => { /* action already handled in filter block */ }, ); // convert template properties to jsonSchema - if (instanceConfig.plugin && - instanceConfig.plugin.template && - instanceConfig.plugin.template.properties) { + if (instanceConfig.plugin + && instanceConfig.plugin.template + && instanceConfig.plugin.template.properties) { instanceConfig.description.dataSchema = toJsonSchema( - instanceConfig.plugin.template.properties) + instanceConfig.plugin.template.properties, + ); } // check description values and upload it const envelope: Envelope = { - public: JSON.parse(JSON.stringify(instanceConfig.description || Container.defaultDescription)), + public: JSON.parse( + JSON.stringify(instanceConfig.description || Container.defaultDescription), + ), }; // ensure abi definition is saved to the data container if (!envelope.public.abis) { envelope.public.abis = { - own: JSON.parse(options.contractLoader.contracts.DataContract.interface) + own: JSON.parse(options.contractLoader.contracts.DataContract.interface), }; } @@ -311,8 +325,8 @@ export class Container extends Logger { // create contract const contract = await options.dataContract.create( - instanceConfig.factoryAddress || - options.nameResolver.getDomainName(options.nameResolver.config.domains.containerFactory), + instanceConfig.factoryAddress + || options.nameResolver.getDomainName(options.nameResolver.config.domains.containerFactory), instanceConfig.accountId, null, '0x0000000000000000000000000000000000000000000000000000000000000000', @@ -328,26 +342,22 @@ export class Container extends Logger { // now check all remaining identities if they match the new created contract const identityHolderContract = options.contractLoader.loadContract( 'IdentityHolder', - contractIdentities + contractIdentities, ); - const resolvedIdentities = await Promise.all(pendingIdentities.map(async (pendingIdentity) => { - return { - identity: pendingIdentity, - contract: await options.executor.executeContractCall( - identityHolderContract, - 'getLink', - pendingIdentity - ) - } - })); - const targetIdentity = resolvedIdentities.find((resolvedIdentity) => { - return new RegExp(`${contractId.substr(2)}$`, 'i').test(resolvedIdentity.contract); - }); + const resolvedIdentities = await Promise.all(pendingIdentities.map(async (pendingIdentity) => ({ + identity: pendingIdentity, + contract: await options.executor.executeContractCall( + identityHolderContract, + 'getLink', + pendingIdentity, + ), + }))); + const targetIdentity = resolvedIdentities.find((resolvedIdentity) => new RegExp(`${contractId.substr(2)}$`, 'i').test(resolvedIdentity.contract)); // after found the correct identity, stop the subscription options.executor.eventHub.unsubscribe({ - subscription: identitiesSubscription + subscription: identitiesSubscription, }); pendingIdentities.splice(pendingIdentities.indexOf(targetIdentity.identity), 1); @@ -367,7 +377,7 @@ export class Container extends Logger { */ public static async deleteContainerPlugin( profile: Profile, - name: string + name: string, ): Promise { // force reload to work on latest tree await profile.loadForAccount(profile.treeLabels.dtContainerPlugins); @@ -389,7 +399,7 @@ export class Container extends Logger { */ public static async getContainerPlugin( profile: Profile, - name: string + name: string, ): Promise { return (await this.getContainerPlugins(profile))[name]; } @@ -400,7 +410,7 @@ export class Container extends Logger { * @param {Profile} profile profile instance */ public static async getContainerPlugins( - profile: Profile + profile: Profile, ): Promise<{[id: string]: ContainerPlugin}> { return (await profile.getPlugins() || { }); } @@ -472,7 +482,8 @@ export class Container extends Logger { (async () => { const toSet = await this.encryptFilesIfRequired(listName, values); await this.options.dataContract.addListEntries( - this.contract, listName, toSet, this.config.accountId) + this.contract, listName, toSet, this.config.accountId, + ); })(), ); } @@ -488,7 +499,7 @@ export class Container extends Logger { await this.ensureContract(); const owner = await this.options.executor.executeContractCall(this.contract, 'owner'); const isOwner = owner === this.config.accountId; - await Throttle.all(verifications.map(verification => async () => { + await Throttle.all(verifications.map((verification) => async () => { const verificationId = await this.options.verifications.setVerification( this.config.accountId, this.contract.options.address, @@ -509,11 +520,11 @@ export class Container extends Logger { })); // update description if current user is owner if (owner === this.config.accountId) { - const tags = verifications.map(verification => `verification:${verification.topic}`); + const tags = verifications.map((verification) => `verification:${verification.topic}`); await this.getMutex('description').runExclusive(async () => { const description = await this.getDescription(); const oldTags = description.tags || []; - const toAdd = tags.filter(tag => !oldTags.includes(tag)); + const toAdd = tags.filter((tag) => !oldTags.includes(tag)); if (toAdd.length) { description.tags = oldTags.concat(toAdd); await this.setDescription(description); @@ -588,7 +599,8 @@ export class Container extends Logger { public async getDescription(): Promise { await this.ensureContract(); return (await this.options.description.getDescription( - this.contract.options.address, this.config.accountId)).public; + this.contract.options.address, this.config.accountId, + )).public; } /** @@ -602,7 +614,8 @@ export class Container extends Logger { 'get entry', (async () => { let value = await this.options.dataContract.getEntry( - this.contract, entryName, this.config.accountId); + this.contract, entryName, this.config.accountId, + ); if (entryName !== 'type') { value = await this.decryptFilesIfRequired(entryName, value); @@ -623,14 +636,19 @@ export class Container extends Logger { * @param {number} offset skip this many entries * @param {boolean} reverse if true, fetches last items first */ - public async getListEntries(listName: string, count = 10, offset = 0, reverse = false + public async getListEntries( + listName: string, + count = 10, + offset = 0, + reverse = false, ): Promise { await this.ensureContract(); return this.wrapPromise( 'get list entries', (async () => { const values = await this.options.dataContract.getListEntries( - this.contract, listName, this.config.accountId, true, true, count, offset, reverse); + this.contract, listName, this.config.accountId, true, true, count, offset, reverse, + ); return this.decryptFilesIfRequired(listName, values); })(), ); @@ -648,7 +666,8 @@ export class Container extends Logger { 'get list entry', (async () => { const value = await this.options.dataContract.getListEntry( - this.contract, listName, index, this.config.accountId); + this.contract, listName, index, this.config.accountId, + ); return this.decryptFilesIfRequired(listName, value); })(), ); @@ -698,10 +717,11 @@ export class Container extends Logger { accountId, '0x0000000000000000000000000000000000000000', this.options.rightsAndRoles.getOperationCapabilityHash( - property, enumType, ModificationType.Set), + property, enumType, ModificationType.Set, + ), ); }; - const tasks = Object.keys(description.dataSchema).map(property => async () => { + const tasks = Object.keys(description.dataSchema).map((property) => async () => { if (property === 'type') { // do not list type property return; @@ -730,8 +750,9 @@ export class Container extends Logger { await this.ensureContract(); const roleMap = await this.options.rightsAndRoles.getMembers(this.contract); const unique = Array.from(new Set([].concat(...Object.values(roleMap)))); - return Throttle.all(unique.map(accountId => async () => - this.getContainerShareConfigForAccount(accountId))); + return Throttle.all( + unique.map((accountId) => async () => this.getContainerShareConfigForAccount(accountId)), + ); } /** @@ -740,10 +761,10 @@ export class Container extends Logger { public async getOwner(): Promise { const authContract = this.options.contractLoader.loadContract( 'DSAuth', - await this.getContractAddress() + await this.getContractAddress(), ); - return await this.options.executor.executeContractCall(authContract, 'owner') + return this.options.executor.executeContractCall(authContract, 'owner'); } /** @@ -754,14 +775,13 @@ export class Container extends Logger { const description = await this.getDescription(); const tags = description.tags || []; return Throttle.all(tags - .filter(tag => tag.startsWith('verification:')) - .map(tag => tag.substr(13)) - .map(topic => async () => this.options.verifications.getVerifications( + .filter((tag) => tag.startsWith('verification:')) + .map((tag) => tag.substr(13)) + .map((topic) => async () => this.options.verifications.getVerifications( description.identity, topic, true, - )) - ); + ))); } /** @@ -779,25 +799,24 @@ export class Container extends Logger { // support short hand for removing a single property, ensure that entries variable is an // array - if (!Array.isArray(entries)) { - entries = [ entries ]; + let entriesParameter = entries; + if (!Array.isArray(entriesParameter)) { + entriesParameter = [entriesParameter]; } - // check for list entries, removeListEntries permissions only form them + // check for list entries, removeListEntries permissions only form them const schemaProperties = (await this.toPlugin(false)).template.properties; - const listEntries = entries.filter(entry => schemaProperties[entry].type === 'list'); + const listEntries = entriesParameter.filter((entry) => schemaProperties[entry].type === 'list'); // unshare all accounts from the specific roles - await this.unshareProperties(unique.map(accountId => { - return { - accountId, - readWrite: entries as string[], - removeListEntries: listEntries as string[], - write: entries as string[], - // force removement of the owner - force: true, - }; - })); + await this.unshareProperties(unique.map((accountId) => ({ + accountId, + readWrite: entriesParameter as string[], + removeListEntries: listEntries as string[], + write: entriesParameter as string[], + // force removement of the owner + force: true, + }))); } /** @@ -813,53 +832,56 @@ export class Container extends Logger { */ public async setContainerShareConfigs( newConfigs: ContainerShareConfig | ContainerShareConfig[], - originalConfigs?: ContainerShareConfig | ContainerShareConfig[] + originalConfigs?: ContainerShareConfig | ContainerShareConfig[], ) { // collect all users that properties should be shared / unshared - const shareConfigs: Array = [ ]; - const unshareConfigs: Array = [ ]; - + const shareConfigs: ContainerShareConfig[] = []; + const unshareConfigs: ContainerShareConfig[] = []; + let configsParameter = newConfigs; + let originalConfigsParameter = originalConfigs; // ensure working with arrays - if (!Array.isArray(newConfigs)) { - newConfigs = [ newConfigs ]; + if (!Array.isArray(configsParameter)) { + configsParameter = [configsParameter]; } - if (!Array.isArray(originalConfigs)) { - originalConfigs = originalConfigs ? [ originalConfigs ] : [ ]; + if (!Array.isArray(originalConfigsParameter)) { + originalConfigsParameter = originalConfigsParameter ? [originalConfigsParameter] : []; } // iterate through all configurations and check for sharing updates - await Promise.all(newConfigs.map(async (newConfig: ContainerShareConfig) => { - // objects to store sharing configuration delta (accountId, read, readWrite, removeListEntries) - const shareConfig: ContainerShareConfig = { accountId: newConfig.accountId }; - const unshareConfig: ContainerShareConfig = { accountId: newConfig.accountId }; - let originalConfig = (originalConfigs as ContainerShareConfig[]) - .filter(orgConf => orgConf.accountId === newConfig.accountId)[0]; + await Promise.all(configsParameter.map(async (newConfig: ContainerShareConfig) => { + const newConfigParam = newConfig; + // objects to store sharing configuration delta + // (accountId, read, readWrite, removeListEntries) + const shareConfig: ContainerShareConfig = { accountId: newConfigParam.accountId }; + const unshareConfig: ContainerShareConfig = { accountId: newConfigParam.accountId }; + let originalConfig = (originalConfigsParameter as ContainerShareConfig[]) + .filter((orgConf) => orgConf.accountId === newConfigParam.accountId)[0]; // load latest share configuration to buil delta against if (!originalConfig) { - originalConfig = await this.getContainerShareConfigForAccount(newConfig.accountId); + originalConfig = await this.getContainerShareConfigForAccount(newConfigParam.accountId); } // fill empty sharing types, so further checks will be easier - const shareConfigProperties = [ 'read', 'readWrite', 'removeListEntries' ]; - shareConfigProperties.forEach(type => { - newConfig[type] = newConfig[type] || [ ]; - originalConfig[type] = originalConfig[type] || [ ]; - shareConfig[type] = shareConfig[type] || [ ]; - unshareConfig[type] = unshareConfig[type] || [ ]; + const shareConfigProperties = ['read', 'readWrite', 'removeListEntries']; + shareConfigProperties.forEach((type) => { + newConfigParam[type] = newConfigParam[type] || []; + originalConfig[type] = originalConfig[type] || []; + shareConfig[type] = shareConfig[type] || []; + unshareConfig[type] = unshareConfig[type] || []; }); // iterate through all share config properties and detect changes - shareConfigProperties.forEach(type => { + shareConfigProperties.forEach((type) => { // track new properties that should be shared - newConfig[type].forEach(property => { + newConfigParam[type].forEach((property) => { if (originalConfig[type].indexOf(property) === -1) { shareConfig[type].push(property); } }); // track properties that should be unshared - originalConfig[type].forEach(property => { - if (newConfig[type].indexOf(property) === -1) { + originalConfig[type].forEach((property) => { + if (newConfigParam[type].indexOf(property) === -1) { unshareConfig[type].push(property); } }); @@ -889,7 +911,8 @@ export class Container extends Logger { await this.wrapPromise( 'set description', this.options.description.setDescription( - this.contract.options.address, { public: description }, this.config.accountId), + this.contract.options.address, { public: description }, this.config.accountId, + ), ); } @@ -918,47 +941,57 @@ export class Container extends Logger { */ public async shareProperties(shareConfigs: ContainerShareConfig[]): Promise { await this.ensureContract(); - ///////////////////////////////////////////////////////////////////////////// check requirements + // check requirements // check ownership const authority = this.options.contractLoader.loadContract( 'DSRolesPerContract', await this.options.executor.executeContractCall(this.contract, 'authority'), ); if (!await this.options.executor.executeContractCall( - authority, 'hasUserRole', this.config.accountId, 0)) { - throw new Error(`current account "${this.config.accountId}" is unable to share properties, ` + - `as it isn't owner of the underlying contract "${this.contract.options.address}"`); + authority, 'hasUserRole', this.config.accountId, 0, + ) + ) { + throw new Error(`current account "${this.config.accountId}" is unable to share properties, ` + + `as it isn't owner of the underlying contract "${this.contract.options.address}"`); } // check fields const localShareConfig = cloneDeep(shareConfigs); const schemaProperties = (await this.toPlugin(false)).template.properties; const sharedProperties = Array.from( - new Set([].concat(...localShareConfig.map(shareConfig => [].concat( - shareConfig.read, shareConfig.readWrite))))) - .filter(property => property !== undefined); + new Set([].concat(...localShareConfig.map((shareConfig) => [].concat( + shareConfig.read, shareConfig.readWrite, + )))), + ) + .filter((property) => property !== undefined); const missingProperties = sharedProperties - .filter(property => !schemaProperties.hasOwnProperty(property)); + .filter((property) => !Object.prototype.hasOwnProperty.call(schemaProperties, property)); if (missingProperties.length) { throw new Error( - `tried to share properties, but missing one or more in schema: ${missingProperties}`); + `tried to share properties, but missing one or more in schema: ${missingProperties}`, + ); } // for all share configs - for (let { accountId, read = [], readWrite = [], removeListEntries = [] } of localShareConfig) { - //////////////////////////////////////////////////// ensure that account is member in contract - if (! await this.options.executor.executeContractCall( - this.contract, 'isConsumer', accountId)) { + for (const { + accountId, read = [], readWrite = [], removeListEntries = [], + } of localShareConfig) { + // //////////////////////////////////////////////// ensure that account is member in contract + if (!await this.options.executor.executeContractCall( + this.contract, 'isConsumer', accountId, + ) + ) { await this.options.dataContract.inviteToContract( - null, this.contract.options.address, this.config.accountId, accountId); + null, this.contract.options.address, this.config.accountId, accountId, + ); } - ///////////////////////////////////////////////////////// ensure property roles and membership + // ///////////////////////////////////////////////////// ensure property roles and membership // share type every time as it is mandatory if (!read.includes('type')) { read.push('type'); } // ensure that roles for fields exist and that accounts have permissions - for (let property of readWrite) { + for (const property of readWrite) { // get permissions from contract const hash = this.options.rightsAndRoles.getOperationCapabilityHash( property, @@ -994,15 +1027,17 @@ export class Container extends Logger { // ensure that account has role const hasRole = await this.options.executor.executeContractCall( - authority, 'hasUserRole', accountId, permittedRole); + authority, 'hasUserRole', accountId, permittedRole, + ); if (!hasRole) { await this.options.rightsAndRoles.addAccountToRole( - this.contract, this.config.accountId, accountId, permittedRole); + this.contract, this.config.accountId, accountId, permittedRole, + ); } } - for (let property of removeListEntries) { + for (const property of removeListEntries) { const propertyType = getPropertyType(schemaProperties[property].type); // throw error if remove should be given on no list @@ -1045,19 +1080,21 @@ export class Container extends Logger { // ensure that account has role const hasRole = await this.options.executor.executeContractCall( - authority, 'hasUserRole', accountId, permittedRole); + authority, 'hasUserRole', accountId, permittedRole, + ); if (!hasRole) { await this.options.rightsAndRoles.addAccountToRole( - this.contract, this.config.accountId, accountId, permittedRole); + this.contract, this.config.accountId, accountId, permittedRole, + ); } } // ensure that content keys were created for all shared properties await Promise.all([...read, ...readWrite].map( - property => this.ensureKeyInSharing(property) + (property) => this.ensureKeyInSharing(property), )); - //////////////////////////////////////////////////////// ensure encryption keys for properties + // //////////////////////////////////////////////////// ensure encryption keys for properties // run with mutex to prevent breaking sharing info await this.getMutex('sharing').runExclusive(async () => { // checkout sharings @@ -1066,9 +1103,9 @@ export class Container extends Logger { // check if account already has a hash key const sha3 = (...args) => this.options.nameResolver.soliditySha3(...args); const isShared = (section, block?) => { - if (!sharings[sha3(accountId)] || - !sharings[sha3(accountId)][sha3(section)] || - (typeof block !== 'undefined' && !sharings[sha3(accountId)][sha3(section)][block])) { + if (!sharings[sha3(accountId)] + || !sharings[sha3(accountId)][sha3(section)] + || (typeof block !== 'undefined' && !sharings[sha3(accountId)][sha3(section)][block])) { return false; } return true; @@ -1076,30 +1113,35 @@ export class Container extends Logger { let modified = false; if (!isShared('*', 'hashKey')) { const hashKeyToShare = await this.options.sharing.getHashKey( - this.contract.options.address, this.config.accountId); + this.contract.options.address, this.config.accountId, + ); await this.options.sharing.extendSharings( - sharings, this.config.accountId, accountId, '*', 'hashKey', hashKeyToShare, null); + sharings, this.config.accountId, accountId, '*', 'hashKey', hashKeyToShare, null, + ); modified = true; } // ensure that target user has sharings for properties const blockNr = await this.options.web3.eth.getBlockNumber(); // share keys for read and readWrite - for (let property of [...read, ...readWrite]) { + for (const property of [...read, ...readWrite]) { if (!isShared(property)) { // get key const contentKey = await this.options.sharing.getKey( - this.contract.options.address, this.config.accountId, property, blockNr); + this.contract.options.address, this.config.accountId, property, blockNr, + ); // share this key await this.options.sharing.extendSharings( - sharings, this.config.accountId, accountId, property, 0, contentKey); + sharings, this.config.accountId, accountId, property, 0, contentKey, + ); modified = true; } } if (modified) { // store sharings await this.options.sharing.saveSharingsToContract( - this.contract.options.address, sharings, this.config.accountId); + this.contract.options.address, sharings, this.config.accountId, + ); } }); } @@ -1132,9 +1174,9 @@ export class Container extends Logger { type = this.deriveSchema(data[property]).type; } // add field or entry, based on property type - await (type === 'array' ? - this.addListEntries(property, data[property]) : - this.setEntry(property, data[property]) + await (type === 'array' + ? this.addListEntries(property, data[property]) + : this.setEntry(property, data[property]) ); }); @@ -1154,23 +1196,23 @@ export class Container extends Logger { // create empty plugin const template: Partial = { - properties: { } + properties: { }, }; const plugin: ContainerPlugin = { description, - template: template as ContainerTemplate + template: template as ContainerTemplate, }; // if values should be loaded, load permissions first, so we won't load unreadable data let readableEntries; if (getValues) { const containerShareConfig = await this.getContainerShareConfigForAccount( - this.config.accountId + this.config.accountId, ); - readableEntries = [ ].concat( + readableEntries = [].concat( containerShareConfig.read || [], - containerShareConfig.readWrite || [ ] + containerShareConfig.readWrite || [], ); } @@ -1179,8 +1221,9 @@ export class Container extends Logger { 'DSRolesPerContract', await this.options.executor.executeContractCall(this.contract, 'authority'), ); - for (let property of Object.keys(description.dataSchema)) { + for (const property of Object.keys(description.dataSchema)) { if (property === 'type') { + // eslint-disable-next-line no-continue continue; } const dataSchema = description.dataSchema[property]; @@ -1198,18 +1241,21 @@ export class Container extends Logger { try { value = await this.getEntry(property); } catch (ex) { - this.log(`Could not load value for entry ${ property } in toPlugin: - ${ ex.message }`, 'error'); + this.log(`Could not load value for entry ${property} in toPlugin: + ${ex.message}`, 'error'); } - if (value && - value !== '0x0000000000000000000000000000000000000000000000000000000000000000') { + if (value + && value !== '0x0000000000000000000000000000000000000000000000000000000000000000') { template.properties[property].value = value; } } } - template.properties[property].permissions = - await this.getRolePermission(authority, property, type); + template.properties[property].permissions = await this.getRolePermission( + authority, + property, + type, + ); } } // write type value to template property @@ -1227,46 +1273,51 @@ export class Container extends Logger { */ public async unshareProperties(unshareConfigs: ContainerUnshareConfig[]): Promise { await this.ensureContract(); - this.log(`unsharing properties`, 'debug'); - ///////////////////////////////////////////////////////////////////////////// check requirements + this.log('unsharing properties', 'debug'); + // ///////////////////////////////////////////////////////////////////////// check requirements // check ownership const authority = this.options.contractLoader.loadContract( 'DSRolesPerContract', await this.options.executor.executeContractCall(this.contract, 'authority'), ); if (!await this.options.executor.executeContractCall( - authority, 'hasUserRole', this.config.accountId, 0)) { - throw new Error(`current account "${this.config.accountId}" is unable to unshare ` + - 'properties, as it isn\'t owner of the underlying contract ' + - this.contract.options.address); + authority, 'hasUserRole', this.config.accountId, 0, + )) { + throw new Error(`${`current account "${this.config.accountId}" is unable to unshare ` + + 'properties, as it isn\'t owner of the underlying contract '}${ + this.contract.options.address}`); } // only allow owner removal when force attribute is set const unpermittedOwnerRemoval = unshareConfigs.filter( - unshareConfig => unshareConfig.accountId === this.config.accountId && !unshareConfig.force); - if (unpermittedOwnerRemoval.length !==0) { - throw new Error(`current account "${this.config.accountId}" is owner of the contract ` + - 'and cannot remove himself from sharing without force attribute' + - this.contract.options.address); + (unshareConfig) => unshareConfig.accountId === this.config.accountId && !unshareConfig.force, + ); + if (unpermittedOwnerRemoval.length !== 0) { + throw new Error(`${`current account "${this.config.accountId}" is owner of the contract ` + + 'and cannot remove himself from sharing without force attribute'}${ + this.contract.options.address}`); } // check fields const localUnshareConfigs = cloneDeep(unshareConfigs); const schemaProperties = (await this.toPlugin(false)).template.properties; const sharedProperties = Array.from( - new Set([].concat(...localUnshareConfigs.map(unshareConfig => [].concat( - unshareConfig.write, unshareConfig.readWrite))))) - .filter(property => property !== undefined); + new Set([].concat(...localUnshareConfigs.map((unshareConfig) => [].concat( + unshareConfig.write, unshareConfig.readWrite, + )))), + ) + .filter((property) => property !== undefined); const missingProperties = sharedProperties - .filter(property => !schemaProperties.hasOwnProperty(property)); + .filter((property) => !Object.prototype.hasOwnProperty.call(schemaProperties, property)); if (missingProperties.length) { throw new Error( - `tried to share properties, but missing one or more in schema: ${missingProperties}`); + `tried to share properties, but missing one or more in schema: ${missingProperties}`, + ); } // ensure if removeListEntries can be removed correctly beforehand - for (let { removeListEntries = [], } of localUnshareConfigs) { - for (let property of removeListEntries) { + for (const { removeListEntries = [] } of localUnshareConfigs) { + for (const property of removeListEntries) { const propertyType = getPropertyType(schemaProperties[property].type); // throw error if remove should be given on no list @@ -1275,8 +1326,9 @@ export class Container extends Logger { } // search for role with permissions - let permittedRole = await this.getPermittedRole( - authority, property, propertyType, ModificationType.Remove); + const permittedRole = await this.getPermittedRole( + authority, property, propertyType, ModificationType.Remove, + ); if (permittedRole < this.reservedRoles) { // if not found or included in reserved roles, exit throw new Error(`can not find a role that has remove permissions for list "${property}"`); @@ -1285,32 +1337,38 @@ export class Container extends Logger { } // for all share configs - for (let { accountId, readWrite = [], removeListEntries = [], write = [] } of localUnshareConfigs) { - this.log(`checking unshare configs`, 'debug'); + for (const { + accountId, readWrite = [], removeListEntries = [], write = [], + } of localUnshareConfigs) { + this.log('checking unshare configs', 'debug'); // remove write permissions for all in readWrite and write - for (let property of [...readWrite, ...write]) { + for (const property of [...readWrite, ...write]) { this.log(`removing write permissions for ${property}`, 'debug'); const propertyType = getPropertyType(schemaProperties[property].type); // search for role with permissions - let permittedRole = await this.getPermittedRole( - authority, property, propertyType, ModificationType.Set); + const permittedRole = await this.getPermittedRole( + authority, property, propertyType, ModificationType.Set, + ); if (permittedRole < this.reservedRoles) { // if not found or included in reserved roles, exit - this.log('can not find a role that has write permissions for property ' + property); + this.log(`can not find a role that has write permissions for property ${property}`); } else { // remove account from role const hasRole = await this.options.executor.executeContractCall( - authority, 'hasUserRole', accountId, permittedRole); + authority, 'hasUserRole', accountId, permittedRole, + ); if (hasRole) { await this.options.rightsAndRoles.removeAccountFromRole( - this.contract, this.config.accountId, accountId, permittedRole); + this.contract, this.config.accountId, accountId, permittedRole, + ); } // if no members are left, remove role const memberCount = await this.options.executor.executeContractCall( - authority, 'role2userCount', permittedRole); + authority, 'role2userCount', permittedRole, + ); if (memberCount.eq(0)) { - this.log(`removing role for property "${property}"`, 'debug') + this.log(`removing role for property "${property}"`, 'debug'); await this.options.rightsAndRoles.setOperationPermission( authority, this.config.accountId, @@ -1324,7 +1382,7 @@ export class Container extends Logger { } } - /////////////////////// check if only remaining property is 'type', cleanup if that's the case + // /////////////////// check if only remaining property is 'type', cleanup if that's the case const shareConfig = await this.getContainerShareConfigForAccount(accountId); const remainingFields = Array.from(new Set([ ...(shareConfig.read ? shareConfig.read : []), @@ -1332,36 +1390,43 @@ export class Container extends Logger { ])); if (remainingFields.length === 1 && remainingFields[0] === 'type') { // remove property - let permittedRole = await this.getPermittedRole( - authority, 'type', PropertyType.Entry, ModificationType.Set); + const permittedRole = await this.getPermittedRole( + authority, 'type', PropertyType.Entry, ModificationType.Set, + ); await this.options.rightsAndRoles.removeAccountFromRole( - this.contract, this.config.accountId, accountId, permittedRole); + this.contract, this.config.accountId, accountId, permittedRole, + ); // remove read if applicable readWrite.push('type'); // uninvite this.options.dataContract.removeFromContract( - null, await this.getContractAddress(), this.config.accountId, accountId); + null, await this.getContractAddress(), this.config.accountId, accountId, + ); } - ///////////////////////////////////////////////////////////////// remove list entries handling - for (let property of removeListEntries) { + // ///////////////////////////////////////////////////////////// remove list entries handling + for (const property of removeListEntries) { const propertyType = getPropertyType(schemaProperties[property].type); - let permittedRole = await this.getPermittedRole( - authority, property, propertyType, ModificationType.Remove); + const permittedRole = await this.getPermittedRole( + authority, property, propertyType, ModificationType.Remove, + ); // remove account from role const hasRole = await this.options.executor.executeContractCall( - authority, 'hasUserRole', accountId, permittedRole); + authority, 'hasUserRole', accountId, permittedRole, + ); if (hasRole) { await this.options.rightsAndRoles.removeAccountFromRole( - this.contract, this.config.accountId, accountId, permittedRole); + this.contract, this.config.accountId, accountId, permittedRole, + ); } // if no members are left, remove role const memberCount = await this.options.executor.executeContractCall( - authority, 'role2userCount', permittedRole); + authority, 'role2userCount', permittedRole, + ); if (memberCount.eq(0)) { await this.options.rightsAndRoles.setOperationPermission( authority, @@ -1375,19 +1440,19 @@ export class Container extends Logger { } } - //////////////////////////////////////////////////////// ensure encryption keys for properties + // //////////////////////////////////////////////////// ensure encryption keys for properties // run with mutex to prevent breaking sharing info await this.getMutex('sharing').runExclusive(async () => { - this.log(`checking read permisssions`, 'debug'); + this.log('checking read permisssions', 'debug'); // checkout sharings const sharings = await this.options.sharing.getSharingsFromContract(this.contract); // check if account already has a hash key const sha3 = (...args) => this.options.nameResolver.soliditySha3(...args); const isShared = (section, block?) => { - if (!sharings[sha3(accountId)] || - !sharings[sha3(accountId)][sha3(section)] || - (typeof block !== 'undefined' && !sharings[sha3(accountId)][sha3(section)][block])) { + if (!sharings[sha3(accountId)] + || !sharings[sha3(accountId)][sha3(section)] + || (typeof block !== 'undefined' && !sharings[sha3(accountId)][sha3(section)][block])) { return false; } return true; @@ -1395,7 +1460,7 @@ export class Container extends Logger { let modified = false; // remove keys for readWrite - for (let property of readWrite) { + for (const property of readWrite) { this.log(`checking read permissions for ${property}`, 'debug'); if (isShared(property)) { this.log(`removing key for ${property}`, 'debug'); @@ -1405,22 +1470,23 @@ export class Container extends Logger { } // cleanup sharings, this relies on a few conditions, // that are met, when sharings are managed by Container API - if (sharings[sha3(accountId)] && // account has any sharing - Object.keys(sharings[sha3(accountId)]).length === 2 && // only 2 sections remain - sharings[sha3(accountId)][sha3('*')] && // * section remains - Object.keys(sharings[sha3(accountId)][sha3('*')]).length === 1 && // only 1 entry in * - sharings[sha3(accountId)][sha3('*')].hashKey && // only the hash key in * - sharings[sha3(accountId)][sha3('type')]) { // type remains - this.log(`account from sharings`, 'debug'); + if (sharings[sha3(accountId)] // account has any sharing + && Object.keys(sharings[sha3(accountId)]).length === 2 // only 2 sections remain + && sharings[sha3(accountId)][sha3('*')] // * section remains + && Object.keys(sharings[sha3(accountId)][sha3('*')]).length === 1 // only 1 entry in * + && sharings[sha3(accountId)][sha3('*')].hashKey // only the hash key in * + && sharings[sha3(accountId)][sha3('type')]) { // type remains + this.log('account from sharings', 'debug'); await this.options.sharing.trimSharings(sharings, accountId); modified = true; } if (modified) { - this.log(`sharings updated, saving sharings`, 'debug'); + this.log('sharings updated, saving sharings', 'debug'); // store sharings await this.options.sharing.saveSharingsToContract( - this.contract.options.address, sharings, this.config.accountId); + this.contract.options.address, sharings, this.config.accountId, + ); } // inner mutex, as we also rely on sharings @@ -1428,11 +1494,11 @@ export class Container extends Logger { // check if field still exists in updated sharing, if not remove field schema const description = await this.getDescription(); const propertyHashes = []; - for (let accountHash of Object.keys(sharings)) { + for (const accountHash of Object.keys(sharings)) { propertyHashes.push(...Object.keys(sharings[accountHash])); } modified = false; - for (let property of readWrite) { + for (const property of readWrite) { if (!propertyHashes.includes(this.options.nameResolver.soliditySha3(property))) { delete description.dataSchema[property]; modified = true; @@ -1458,33 +1524,34 @@ export class Container extends Logger { if (this.isEncryptedFile(subSchema)) { // simple entry return toApply(toInspect); - } else if (subSchema.type === 'array' && this.isEncryptedFile(subSchema.items)) { + } if (subSchema.type === 'array' && this.isEncryptedFile(subSchema.items)) { // list/array with entries - return Promise.all(toInspect.map(entry => toApply(entry))); - } else { - // check if nested - if (subSchema.type === 'array') { - return Promise.all(toInspect.map(entry => this.applyIfEncrypted( - subSchema.items, entry, toApply))); - } else if (subSchema.type === 'object') { - // check objects subproperties - const transformed = {}; - for (let key of Object.keys(toInspect)) { - // traverse further, if suproperties are defined - if (subSchema.properties && subSchema.properties[key]) { - // if included in schema, drill down - transformed[key] = await this.applyIfEncrypted( - subSchema.properties[key], toInspect[key], toApply); - } else { - // if not in schema, just copy - transformed[key] = toInspect[key]; - } + return Promise.all(toInspect.map((entry) => toApply(entry))); + } + // check if nested + if (subSchema.type === 'array') { + return Promise.all(toInspect.map((entry) => this.applyIfEncrypted( + subSchema.items, entry, toApply, + ))); + } if (subSchema.type === 'object') { + // check objects subproperties + const transformed = {}; + for (const key of Object.keys(toInspect)) { + // traverse further, if suproperties are defined + if (subSchema.properties && subSchema.properties[key]) { + // if included in schema, drill down + transformed[key] = await this.applyIfEncrypted( + subSchema.properties[key], toInspect[key], toApply, + ); + } else { + // if not in schema, just copy + transformed[key] = toInspect[key]; } - return transformed; } - // no encryption required - return toInspect; + return transformed; } + // no encryption required + return toInspect; } /** @@ -1497,13 +1564,12 @@ export class Container extends Logger { if (value === '0x0000000000000000000000000000000000000000000000000000000000000000') { return value; } - let result = value; const description = await this.getDescription(); if (!description.dataSchema || !description.dataSchema[propertyName]) { throw new Error(`could not find description for entry "${propertyName}"`); } const decrypt = async (toEncrypt) => { - const encryptedFiles = await Throttle.all(toEncrypt.files.map(file => async () => { + const encryptedFiles = await Throttle.all(toEncrypt.files.map((file) => async () => { try { JSON.parse(file); } catch (ex) { @@ -1522,9 +1588,7 @@ export class Container extends Logger { return { files: encryptedFiles.filter((f) => f !== null) }; }; - result = await this.applyIfEncrypted(description.dataSchema[propertyName], value, decrypt); - - return result; + return this.applyIfEncrypted(description.dataSchema[propertyName], value, decrypt); } /** @@ -1540,7 +1604,7 @@ export class Container extends Logger { throw new Error('could not derive type of value; cyclic references detected'); } let schema; - let type = typeof value; + const type = typeof value; if (['boolean', 'number', 'string'].includes(type)) { schema = { type }; } else if (type === 'object') { @@ -1566,7 +1630,6 @@ export class Container extends Logger { propertyName: string, value: ContainerFile[]|any, ): Promise { - let result = value; const description = await this.getDescription(); if (!description.dataSchema || !description.dataSchema[propertyName]) { throw new Error(`could not find description for entry "${propertyName}"`); @@ -1575,24 +1638,22 @@ export class Container extends Logger { // encrypt files and map them into the correct object format const blockNr = await this.options.web3.eth.getBlockNumber(); const encrypt = async (toEncrypt) => { - const encryptedFiles = await Throttle.all(toEncrypt.files.map(file => async () => - this.options.dataContract.encrypt( + const encryptedFiles = await Throttle.all( + toEncrypt.files.map((file) => async () => this.options.dataContract.encrypt( { private: file }, this.contract, this.config.accountId, propertyName, blockNr, 'aesBlob', - ) - )); + )), + ); return { files: encryptedFiles }; }; // check for encrypted files - result = await this.applyIfEncrypted(description.dataSchema[propertyName], value, encrypt); - - return result; + return this.applyIfEncrypted(description.dataSchema[propertyName], value, encrypt); } /** @@ -1603,9 +1664,9 @@ export class Container extends Logger { return; } checkConfigProperties(this.config, ['address']); - let address = this.config.address.startsWith('0x') ? - this.config.address : await this.options.nameResolver.getAddress(this.config.address); - this.contract = this.options.contractLoader.loadContract('DataContract', address) + const address = this.config.address.startsWith('0x') + ? this.config.address : await this.options.nameResolver.getAddress(this.config.address); + this.contract = this.options.contractLoader.loadContract('DataContract', address); } /** @@ -1615,7 +1676,8 @@ export class Container extends Logger { */ private async ensureKeyInSharing(entryName: string): Promise { let key = await this.options.sharing.getKey( - this.contract.options.address, this.config.accountId, entryName); + this.contract.options.address, this.config.accountId, entryName, + ); if (!key) { const cryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo('aes'); key = await cryptor.generateKey(); @@ -1660,7 +1722,7 @@ export class Container extends Logger { await this.options.executor.executeContractCall(this.contract, 'authority'), ); const enumType = getPropertyType(type); - let canSetField = await this.options.executor.executeContractCall( + const canSetField = await this.options.executor.executeContractCall( authority, 'canCallOperation', accountId, @@ -1668,8 +1730,8 @@ export class Container extends Logger { this.options.rightsAndRoles.getOperationCapabilityHash(name, enumType, ModificationType.Set), ); if (!canSetField) { - this.log(`adding permissions on ${type} "${name}" for role ${role} to enable account ` + - `"${accountId}" on container "${this.contract.address}"`, 'debug'); + this.log(`adding permissions on ${type} "${name}" for role ${role} to enable account ` + + `"${accountId}" on container "${this.contract.address}"`, 'debug'); await this.options.rightsAndRoles.setOperationPermission( this.contract, this.config.accountId, @@ -1706,9 +1768,9 @@ export class Container extends Logger { */ private getOperationHash(name: string, type: string, operation = 'set'): string { const keccak256 = this.options.web3.utils.soliditySha3; - const label = type === 'entry' ? - '0x84f3db82fb6cd291ed32c6f64f7f5eda656bda516d17c6bc146631a1f05a1833' : // entry - '0x7da2a80303fd8a8b312bb0f3403e22702ece25aa85a5e213371a770a74a50106'; // list entry + const label = type === 'entry' + ? '0x84f3db82fb6cd291ed32c6f64f7f5eda656bda516d17c6bc146631a1f05a1833' // entry + : '0x7da2a80303fd8a8b312bb0f3403e22702ece25aa85a5e213371a770a74a50106'; // list entry let operationHash; if (operation === 'set') { operationHash = '0xd2f67e6aeaad1ab7487a680eb9d3363a597afa7a3de33fa9bf3ae6edcb88435d'; @@ -1719,7 +1781,10 @@ export class Container extends Logger { } private async getPermittedRole( - authority: any, property: string, propertyType: PropertyType, modificationType: ModificationType + authority: any, + property: string, + propertyType: PropertyType, + modificationType: ModificationType, ): Promise { // get permissions from contract const hash = this.options.rightsAndRoles.getOperationCapabilityHash( @@ -1737,7 +1802,7 @@ export class Container extends Logger { // build string with all roles and 1/0 depending on permissions const binary = (new BigNumber(rolesMap)).toString(2); // check index in group list, ignore reserved roles for this - let index = [...binary.substr(0, binary.length - this.reservedRoles)] + const index = [...binary.substr(0, binary.length - this.reservedRoles)] .reverse().join('').indexOf('1'); // if group was found, re-apply reserved roles count, result is group with permission const permittedRole = (index === -1) ? index : (index + this.reservedRoles); @@ -1752,21 +1817,25 @@ export class Container extends Logger { * @param {string} property property name * @param {string} type 'entry' or 'list' */ - private async getRolePermission(authorityContract: any, property: string, type: string + private async getRolePermission( + authorityContract: any, + property: string, + type: string, ): Promise { const permissions = {}; - for (let operation of ['set', 'remove']) { + for (const operation of ['set', 'remove']) { const rolesMap = await this.options.executor.executeContractCall( authorityContract, 'getOperationCapabilityRoles', this.config.address, this.options.rightsAndRoles.getOperationCapabilityHash( - property, getPropertyType(type), getModificationType(operation)), + property, getPropertyType(type), getModificationType(operation), + ), ); // iterates over all roles and checks which roles are included const checkNumber = (bnum) => { const results = []; - for (let i = 0; i < 256; i++) { + for (let i = 0; i < 256; i += 1) { const divisor = (new BigNumber(2)).pow(i); if (divisor.gt(bnum)) { break; @@ -1779,7 +1848,7 @@ export class Container extends Logger { const roleMap = checkNumber(new BigNumber(rolesMap)); roleMap.forEach((value, i) => { if (value) { - if (!permissions[i]) { + if (!permissions[i]) { permissions[i] = []; } permissions[i].push(operation); @@ -1851,7 +1920,7 @@ async function applyPlugin( dataSchema: { $id: 'type_schema', type: 'string' }, type: 'entry', permissions: { - 0: ['set'] + 0: ['set'], }, value: plugin.template.type, }; @@ -1862,10 +1931,10 @@ async function applyPlugin( operations: [], }; const propertyNames = Object.keys(properties); - for (let propertyName of propertyNames) { + for (const propertyName of propertyNames) { const property: ContainerTemplateProperty = properties[propertyName]; - for (let role of Object.keys(property.permissions)) { - for (let modification of property.permissions[role]) { + for (const role of Object.keys(property.permissions)) { + for (const modification of property.permissions[role]) { permissionUpdates.roles.push(parseInt(role, 10)); permissionUpdates.operations.push(options.rightsAndRoles.getOperationCapabilityHash( propertyName, @@ -1901,13 +1970,14 @@ async function applyPlugin( // write sharings to contract await generateSharings( - options, await container.getContractAddress(), config.accountId, propertyNames); + options, await container.getContractAddress(), config.accountId, propertyNames, + ); // set values after desription has been set const tasks = []; - for (let propertyName of propertyNames) { + for (const propertyName of propertyNames) { const property: ContainerTemplateProperty = properties[propertyName]; - if (property.hasOwnProperty('value')) { + if (Object.prototype.hasOwnProperty.call(property, 'value')) { tasks.push(async () => container.setEntry(propertyName, property.value, false)); } } @@ -1924,7 +1994,9 @@ async function applyPlugin( * @param {string} properties list of property names, that should be present */ function checkConfigProperties(config: ContainerConfig, properties: string[]): void { - let missing = properties.filter(property => !config.hasOwnProperty(property)); + const missing = properties.filter( + (property) => !Object.prototype.hasOwnProperty.call(config, property), + ); if (missing.length === 1) { throw new Error(`missing property in config: "${missing[0]}"`); } else if (missing.length > 1) { @@ -1942,7 +2014,7 @@ function checkConfigProperties(config: ContainerConfig, properties: string[]): v * @param {string[]} fields fields to generate keys for */ async function generateSharings( - runtime: any, contractAddress: string, accountId: string, fields: string[] + runtime: any, contractAddress: string, accountId: string, fields: string[], ) { // get current sharing const sharings = {}; @@ -1955,16 +2027,14 @@ async function generateSharings( // add hash key const tasks = []; - tasks.push(async () => - runtime.sharing.extendSharings( - sharings, accountId, accountId, '*', 'hashKey', keys[keys.length - 1], null) - ); - - for (let i = 0; i < fields.length; i++) { - tasks.push(async () => - runtime.sharing.extendSharings( - sharings, accountId, accountId, fields[i], 0, keys[i]) - ); + tasks.push(async () => runtime.sharing.extendSharings( + sharings, accountId, accountId, '*', 'hashKey', keys[keys.length - 1], null, + )); + + for (let i = 0; i < fields.length; i += 1) { + tasks.push(async () => runtime.sharing.extendSharings( + sharings, accountId, accountId, fields[i], 0, keys[i], + )); } await Throttle.all(tasks); @@ -2009,7 +2079,7 @@ function getPropertyType(typeName: string): PropertyType { function toJsonSchema(properties: any): any { const jsonSchema = {}; - for (let field of Object.keys(properties)) { + for (const field of Object.keys(properties)) { const fieldId = field.replace(/[^a-zA-Z0-9]/g, ''); jsonSchema[field] = { $id: `${fieldId}_schema`, ...properties[field].dataSchema }; } diff --git a/src/contracts/digital-twin/digital-twin.spec.ts b/src/contracts/digital-twin/digital-twin.spec.ts index ce85c3e8..b7596e23 100644 --- a/src/contracts/digital-twin/digital-twin.spec.ts +++ b/src/contracts/digital-twin/digital-twin.spec.ts @@ -27,8 +27,7 @@ import { import { accounts } from '../../test/accounts'; import { configTestcore as config } from '../../config-testcore'; -import { Container, ContainerConfig } from './container'; -import { Ipld } from '../../dfs/ipld'; +import { Container } from './container'; import { TestUtils } from '../../test/test-utils'; import { VerificationsStatus } from '../../verifications/verifications'; import { @@ -44,10 +43,9 @@ use(chaiAsPromised); const ownedDomain = 'twintest.fifs.registrar.test.evan'; -describe('DigitalTwin', function() { +describe('DigitalTwin', function test() { this.timeout(60000); let dfs: Ipfs; - let ipld: Ipld; let defaultConfig: DigitalTwinConfig; let executor: Executor; const description = { @@ -95,7 +93,6 @@ describe('DigitalTwin', function() { // create factory for test const factory = await executor.createContract('DigitalTwinFactory', [], { from: accounts[0], gas: 3e6 }); defaultConfig.factoryAddress = factory.options.address; - console.log(`using twin factory: ${defaultConfig.factoryAddress}`); }); describe('working with twins', () => { @@ -187,17 +184,17 @@ describe('DigitalTwin', function() { it('can get multiple entries from index', async () => { const samples = {}; - for (let i = 0; i < 3; i++) { - samples['sample ' + i.toString().padStart(2, '0')] = { + for (let i = 0; i < 3; i += 1) { + samples[`sample ${i.toString().padStart(2, '0')}`] = { value: TestUtils.getRandomBytes32().replace(/.{4}$/, i.toString().padStart(4, '0')), entryType: DigitalTwinEntryType.Hash, - } - }; + }; + } const twin = await DigitalTwin.create(runtime, defaultConfig); await twin.setEntries(samples); const result = await twin.getEntries(); expect(Object.keys(result).length).to.eq(Object.keys(samples).length); - for (let key of Object.keys(samples)) { + for (const key of Object.keys(samples)) { expect(result[key].value).to.eq(samples[key].value); expect(result[key].entryType).to.eq(samples[key].entryType); } @@ -207,19 +204,19 @@ describe('DigitalTwin', function() { describe('when paging entries', () => { const checkTwin = async (twin, samples) => { const result = await twin.getEntries(); - for (let key of Object.keys(samples)) { + for (const key of Object.keys(samples)) { expect(result[key].value).to.eq(samples[key].value); expect(result[key].entryType).to.eq(samples[key].entryType); } }; const createTwinWithEntries = async (entryCount): Promise => { const samples = {}; - for (let i = 0; i < entryCount; i++) { - samples['sample ' + i.toString().padStart(2, '0')] = { + for (let i = 0; i < entryCount; i += 1) { + samples[`sample ${i.toString().padStart(2, '0')}`] = { value: TestUtils.getRandomBytes32().replace(/.{4}$/, i.toString().padStart(4, '0')), entryType: DigitalTwinEntryType.Hash, - } - }; + }; + } const twin = await DigitalTwin.create(runtime, defaultConfig); await twin.setEntries(samples); return { twin, samples }; @@ -256,7 +253,6 @@ describe('DigitalTwin', function() { const car = await DigitalTwin.create(runtime, defaultConfig); const tire = await DigitalTwin.create(runtime, defaultConfig); - const carAddress = await car.getContractAddress(); const tireAddress = await tire.getContractAddress(); const container = TestUtils.getRandomAddress(); @@ -278,7 +274,6 @@ describe('DigitalTwin', function() { const tire = await DigitalTwin.create(runtime, defaultConfig); const screw = await DigitalTwin.create(runtime, defaultConfig); - const carAddress = await car.getContractAddress(); const tireAddress = await tire.getContractAddress(); const screwAddress = await screw.getContractAddress(); @@ -306,11 +301,12 @@ describe('DigitalTwin', function() { describe('when adding containers', () => { before(async () => { const factory = await executor.createContract( - 'ContainerDataContractFactory', [], { from: accounts[0], gas: 6e6 }); + 'ContainerDataContractFactory', [], { from: accounts[0], gas: 6e6 }, + ); defaultConfig.containerConfig.factoryAddress = factory.options.address; }); - it('creates new containers automatically', async() => { + it('creates new containers automatically', async () => { const twin = await DigitalTwin.create(runtime, defaultConfig); const customPlugin = JSON.parse(JSON.stringify(Container.plugins.metadata)); customPlugin.template.properties.type = { @@ -340,16 +336,18 @@ describe('DigitalTwin', function() { it('can set verifications to twin', async () => { const twin = await DigitalTwin.create(runtime, defaultConfig); const verifications: DigitalTwinVerificationEntry[] = [...Array(3)].map( - (_, i) => ( { topic: `verifcation_${i}` })); + (_, i) => ({ topic: `verifcation_${i}` } as DigitalTwinVerificationEntry), + ); await twin.addVerifications(verifications); const verificationsResults = await twin.getVerifications(); expect(verificationsResults.length).to.eq(3); // all verification lists should have at least 1 valid verification - const allValid = verificationsResults.every(vs => vs.some(v => v.valid)); + const allValid = verificationsResults.every((vs) => vs.some((v) => v.valid)); expect(allValid).to.be.true; // all verifications should be confirmed, as issuing account is owner const allConfirmed = verificationsResults.every( - vs => vs.some(v => v.status === VerificationsStatus.Confirmed)); + (vs) => vs.some((v) => v.status === VerificationsStatus.Confirmed), + ); expect(allConfirmed).to.be.true; }); }); @@ -359,7 +357,8 @@ describe('DigitalTwin', function() { // get address for tests ens = runtime.contractLoader.loadContract('AbstractENS', config.nameResolver.ensAddress); const domainOwner = await executor.executeContractCall( - ens, 'owner', runtime.nameResolver.namehash(ownedDomain)); + ens, 'owner', runtime.nameResolver.namehash(ownedDomain), + ); if (domainOwner === '0x0000000000000000000000000000000000000000') { await runtime.nameResolver.claimAddress(ownedDomain, accounts[0]); } @@ -373,18 +372,20 @@ describe('DigitalTwin', function() { expect(await twin.getContractAddress()).to.match(/0x[0-9a-f]{40}/i); expect(await twin.getContractAddress()).to.eq( - await runtime.nameResolver.getAddress(address)); + await runtime.nameResolver.getAddress(address), + ); }); it('can load indicdes from ENS', async () => { const randomName = Math.floor(Math.random() * 1e12).toString(36); const address = `${randomName}.${ownedDomain}`; - const twin = await DigitalTwin.create(runtime, { ...defaultConfig, address }); + await DigitalTwin.create(runtime, { ...defaultConfig, address }); const loadedTwin = new DigitalTwin(runtime, { ...defaultConfig, address }); expect(await loadedTwin.getContractAddress()).to.match(/0x[0-9a-f]{40}/i); expect(await loadedTwin.getContractAddress()).to.eq( - await runtime.nameResolver.getAddress(address)); + await runtime.nameResolver.getAddress(address), + ); }); it('loading an empty ens address should throw an error', async () => { diff --git a/src/contracts/digital-twin/digital-twin.ts b/src/contracts/digital-twin/digital-twin.ts index bb8020c3..02a3e661 100644 --- a/src/contracts/digital-twin/digital-twin.ts +++ b/src/contracts/digital-twin/digital-twin.ts @@ -20,49 +20,24 @@ import * as Throttle from 'promise-parallel-throttle'; import { Mutex } from 'async-mutex'; import { - ContractLoader, - DfsInterface, Envelope, - Executor, Logger, LoggerOptions, } from '@evan.network/dbcp'; import { Container, ContainerConfig, ContainerOptions } from './container'; -import { DataContract } from '../data-contract/data-contract'; -import { Description } from '../../shared-description'; -import { NameResolver } from '../../name-resolver'; import { Profile } from '../../profile/profile'; -import { RightsAndRoles } from '../rights-and-roles'; -import { Sharing } from '../sharing'; -import { Verifications } from '../../verifications/verifications'; // empty address const nullAddress = '0x0000000000000000000000000000000000000000'; -/** - * Check, that given subset of properties is present at config, collections missing properties and - * throws a single error. - * - * @param {ContainerConfig} config config for container instance - * @param {string} properties list of property names, that should be present - */ -function checkConfigProperties(config: DigitalTwinConfig, properties: string[]): void { - let missing = properties.filter(property => !config.hasOwnProperty(property)); - if (missing.length === 1) { - throw new Error(`missing property in config: "${missing[0]}"`); - } else if (missing.length > 1) { - throw new Error(`missing properties in config: "${missing.join(', ')}"`); - } -} - - /** * possible entry types for entries in index */ export enum DigitalTwinEntryType { AccountId, + // eslint-disable-next-line no-shadow Container, FileHash, GenericContract, @@ -82,7 +57,7 @@ export interface DigitalTwinConfig { address?: string; /** description has to be passed to ``.create`` to apply it to to contract */ description?: any; - /** factory address can be passed to ``.create`` for customer digital twin factory*/ + /** factory address can be passed to ``.create`` for customer digital twin factory */ factoryAddress?: string; } @@ -104,7 +79,8 @@ export interface DigitalTwinIndexEntry { export interface DigitalTwinVerificationEntry { /** name of the verification (full path) */ topic: string; - /** domain of the verification, this is a subdomain under 'verifications.evan', so passing 'example' will link verifications */ + /** domain of the verification, this is a subdomain under 'verifications.evan', + * so passing 'example' will link verifications */ descriptionDomain?: string; /** if true, verifications created under this path are invalid, defaults to ``false`` */ disableSubverifications?: boolean; @@ -134,10 +110,14 @@ export class DigitalTwin extends Logger { version: '0.1.0', dbcpVersion: 2, }; + private config: DigitalTwinConfig; + private contract: any; + private options: DigitalTwinOptions; - private mutexes: { [id: string]: Mutex; }; + + private mutexes: { [id: string]: Mutex }; /** * Create digital twin contract. @@ -152,7 +132,7 @@ export class DigitalTwin extends Logger { const instanceConfig = JSON.parse(JSON.stringify(config)); // ensure, that the evan digital twin tag is set - instanceConfig.description.tags = instanceConfig.description.tags || [ ]; + instanceConfig.description.tags = instanceConfig.description.tags || []; if (instanceConfig.description.tags.indexOf('evan-digital-twin') === -1) { instanceConfig.description.tags.push('evan-digital-twin'); } @@ -170,22 +150,23 @@ export class DigitalTwin extends Logger { if (instanceConfig.address && instanceConfig.address.indexOf('0x') !== 0) { try { const splitEns = config.address.split('.'); - for (let i = splitEns.length - 1; i > -1; i--) { + for (let i = splitEns.length - 1; i > -1; i -= 1) { const checkAddress = splitEns.slice(i, splitEns.length).join('.'); const owner = await options.executor.executeContractCall( - options.nameResolver.ensContract, 'owner', options.nameResolver.namehash(checkAddress)); + options.nameResolver.ensContract, 'owner', options.nameResolver.namehash(checkAddress), + ); if (owner === nullAddress) { await options.nameResolver.setAddress( checkAddress, nullAddress, - instanceConfig.accountId + instanceConfig.accountId, ); } } } catch (ex) { throw new Error(`account is not permitted to create a contract for the ens address - ${ config.address }`); + ${config.address}`); } } @@ -195,12 +176,13 @@ export class DigitalTwin extends Logger { factoryAddress = instanceConfig.factoryAddress; } else { factoryAddress = await options.nameResolver.getAddress( - instanceConfig.factoryAddress || - options.nameResolver.getDomainName(options.nameResolver.config.domains.indexFactory), + instanceConfig.factoryAddress + || options.nameResolver.getDomainName(options.nameResolver.config.domains.indexFactory), ); } const factory = options.contractLoader.loadContract( - 'DigitalTwinFactory', factoryAddress); + 'DigitalTwinFactory', factoryAddress, + ); const contractId = await options.executor.executeContractTransaction( factory, 'createContract', { @@ -234,7 +216,7 @@ export class DigitalTwin extends Logger { * * @param {DigitalTwinOptions} options twin runtime options */ - public static async getFavorites(options: DigitalTwinOptions): Promise> { + public static async getFavorites(options: DigitalTwinOptions): Promise { const favorites = (await options.profile.getBcContracts('twins.evan')) || { }; // purge crypto info directly @@ -253,14 +235,15 @@ export class DigitalTwin extends Logger { public static async getValidity( options: DigitalTwinOptions, ensAddress: string, - ): Promise<{ valid: boolean, exists: boolean, error: Error }> { - let valid = false, exists = false, error = null; + ): Promise<{ valid: boolean; exists: boolean; error: Error }> { + let valid = false; let exists = false; let + error = null; // create temporary twin instance, to ensure the contract const twinInstance = new DigitalTwin(options, { accountId: nullAddress, address: ensAddress, - containerConfig: { accountId: nullAddress } + containerConfig: { accountId: nullAddress }, }); // try to load the contract, this will throw, when the specification is invalid @@ -286,7 +269,7 @@ export class DigitalTwin extends Logger { * @param {DigitalTwinOptions} options runtime-like object with required modules * @param {DigitalTwinConfig} config digital twin related config */ - constructor(options: DigitalTwinOptions, config: DigitalTwinConfig) { + public constructor(options: DigitalTwinOptions, config: DigitalTwinConfig) { super(options as LoggerOptions); this.options = options; this.config = config; @@ -296,7 +279,7 @@ export class DigitalTwin extends Logger { /** * Add the digital twin with given address to profile. */ - async addAsFavorite() { + public async addAsFavorite() { await this.getMutex('profile').runExclusive(async () => { await this.options.profile.loadForAccount(this.options.profile.treeLabels.contracts); await this.options.profile.addBcContract('twins.evan', this.config.address, {}); @@ -313,7 +296,7 @@ export class DigitalTwin extends Logger { await this.ensureContract(); const owner = await this.options.executor.executeContractCall(this.contract, 'owner'); const isOwner = owner === this.config.accountId; - await Throttle.all(verifications.map(verification => async () => { + await Throttle.all(verifications.map((verification) => async () => { const verificationId = await this.options.verifications.setVerification( this.config.accountId, this.contract.options.address, @@ -335,10 +318,10 @@ export class DigitalTwin extends Logger { // update description if current user is owner if (owner === this.config.accountId) { // update description - const verificationTags = verifications.map(verification => `verification:${verification.topic}`); + const verificationTags = verifications.map((verification) => `verification:${verification.topic}`); const description = await this.getDescription(); const oldTags = description.tags || []; - const toAdd = verificationTags.filter(tag => !oldTags.includes(tag)); + const toAdd = verificationTags.filter((tag) => !oldTags.includes(tag)); if (toAdd.length) { description.tags = oldTags.concat(toAdd); await this.setDescription(description); @@ -353,7 +336,8 @@ export class DigitalTwin extends Logger { * create, name is used as * entry name in twin */ - public async createContainers(containers: { [id: string]: Partial } + public async createContainers( + containers: { [id: string]: Partial }, ): Promise<{ [id: string]: Container }> { await this.ensureContract(); const result = {}; @@ -380,30 +364,30 @@ export class DigitalTwin extends Logger { if (this.contract) { return; } - let address = this.config.address.startsWith('0x') ? - this.config.address : await this.options.nameResolver.getAddress(this.config.address); - const baseError = `ens address ${ this.config.address } / contract address ${ address }:`; + const address = this.config.address.startsWith('0x') + ? this.config.address : await this.options.nameResolver.getAddress(this.config.address); + const baseError = `ens address ${this.config.address} / contract address ${address}:`; let description; // if no address is set, throw an error if (!address || address === nullAddress) { - throw new Error(`${ baseError } contract does not exist`); + throw new Error(`${baseError} contract does not exist`); } else { try { description = (await this.options.description .getDescription(address, nullAddress)).public; } catch (ex) { // when the dbcp could not be loaded, throw - this.log(`${ baseError } address ${ address }: Could not load dbcp: - ${ ex.message }`, 'info'); + this.log(`${baseError} address ${address}: Could not load dbcp: + ${ex.message}`, 'info'); } } // if the evan digital twin tag does not exist, throw if (!description || !description.tags || description.tags .indexOf('evan-digital-twin') === -1) { - throw new Error(`${ baseError } doesn't match the specification (missing ` + - 'evan-digital-twin\' tag)'); + throw new Error(`${baseError} doesn't match the specification (missing ` + + 'evan-digital-twin\' tag)'); } this.contract = this.options.contractLoader.loadContract('DigitalTwin', address); @@ -423,7 +407,8 @@ export class DigitalTwin extends Logger { public async getDescription(): Promise { await this.ensureContract(); return (await this.options.description.getDescription( - this.contract.options.address, this.config.accountId)).public; + this.contract.options.address, this.config.accountId, + )).public; } /** @@ -432,7 +417,7 @@ export class DigitalTwin extends Logger { public async getEntries(): Promise<{[id: string]: DigitalTwinIndexEntry}> { await this.ensureContract(); // get all from contract - let results = {}; + const results = {}; let itemsRetrieved = 0; const resultsPerPage = 10; const getResults = async (singleQueryOffset) => { @@ -442,12 +427,11 @@ export class DigitalTwin extends Logger { singleQueryOffset, ); itemsRetrieved += resultsPerPage; - for (let i = 0; i < queryResult.names.length; i++) { + for (let i = 0; i < queryResult.names.length; i += 1) { if (!queryResult.names[i]) { // result pages have empty entries after valid results, so drop those break; } - const resultId = i + singleQueryOffset; results[queryResult.names[i]] = { raw: { value: queryResult.values[i], entryType: queryResult.entryTypes[i] }, }; @@ -457,7 +441,7 @@ export class DigitalTwin extends Logger { } }; await getResults(0); - for (let key of Object.keys(results)) { + for (const key of Object.keys(results)) { this.processEntry(results[key]); } return results; @@ -471,7 +455,7 @@ export class DigitalTwin extends Logger { */ public async getEntry(name: string): Promise { await this.ensureContract(); - const [ , firstKey, remainder ] = /([^/]+)(?:\/(.+))?/.exec(name); + const [, firstKey, remainder] = /([^/]+)(?:\/(.+))?/.exec(name); const result: DigitalTwinIndexEntry = { raw: await this.options.executor.executeContractCall( this.contract, @@ -482,9 +466,8 @@ export class DigitalTwin extends Logger { this.processEntry(result); if (remainder && result.entryType === DigitalTwinEntryType.DigitalTwin) { return result.value.getEntry(remainder); - } else { - return result; } + return result; } /** @@ -495,14 +478,13 @@ export class DigitalTwin extends Logger { const description = await this.getDescription(); const tags = description.tags || []; return Throttle.all(tags - .filter(tag => tag.startsWith('verification:')) - .map(tag => tag.substr(13)) - .map(topic => async () => this.options.verifications.getVerifications( + .filter((tag) => tag.startsWith('verification:')) + .map((tag) => tag.substr(13)) + .map((topic) => async () => this.options.verifications.getVerifications( description.identity, topic, true, - )) - ); + ))); } /** @@ -518,8 +500,6 @@ export class DigitalTwin extends Logger { */ public async removeFromFavorites() { await this.getMutex('profile').runExclusive(async () => { - const description = await this.getDescription(); - await this.options.profile.loadForAccount(this.options.profile.treeLabels.contracts); await this.options.profile.removeBcContract('twins.evan', this.config.address); await this.options.profile.storeForAccount(this.options.profile.treeLabels.contracts); @@ -534,14 +514,16 @@ export class DigitalTwin extends Logger { public async setDescription(description: any): Promise { await this.ensureContract(); await this.getMutex('description').runExclusive(async () => { + const descriptionParam = description; // ensure, that the evan digital twin tag is set - description.tags = description.tags || [ ]; - if (description.tags.indexOf('evan-digital-twin') === -1) { - description.tags.push('evan-digital-twin'); + descriptionParam.tags = descriptionParam.tags || []; + if (descriptionParam.tags.indexOf('evan-digital-twin') === -1) { + descriptionParam.tags.push('evan-digital-twin'); } await this.options.description.setDescription( - this.contract.options.address, { public: description }, this.config.accountId); + this.contract.options.address, { public: descriptionParam }, this.config.accountId, + ); }); } @@ -552,8 +534,11 @@ export class DigitalTwin extends Logger { */ public async setEntries(entries: {[id: string]: DigitalTwinIndexEntry}): Promise { await this.ensureContract(); - await Throttle.all(Object.keys(entries).map((name) => async () => - this.setEntry(name, entries[name].value, entries[name].entryType))); + await Throttle.all( + Object.keys(entries).map( + (name) => async () => this.setEntry(name, entries[name].value, entries[name].entryType), + ), + ); } /** @@ -610,29 +595,30 @@ export class DigitalTwin extends Logger { */ private processEntry(entry: DigitalTwinIndexEntry): void { let address; - entry.entryType = parseInt(entry.raw.entryType, 10); - switch (entry.entryType) { - case DigitalTwinEntryType.AccountId: - case DigitalTwinEntryType.GenericContract: - entry.value = this.options.web3.utils.toChecksumAddress(`0x${entry.raw.value.substr(26)}`); - break; - case DigitalTwinEntryType.Container: - address = this.options.web3.utils.toChecksumAddress(`0x${entry.raw.value.substr(26)}`); - entry.value = new Container( - this.options, - { - accountId: this.config.accountId, - ...this.config.containerConfig, - address, - }, - ); - break; - case DigitalTwinEntryType.DigitalTwin: - address = this.options.web3.utils.toChecksumAddress(`0x${entry.raw.value.substr(26)}`); - entry.value = new DigitalTwin(this.options, { ...this.config, address }); - break; - default: - entry.value = entry.raw.value; + const entryParam = entry; + entryParam.entryType = parseInt(entryParam.raw.entryType, 10); + switch (entryParam.entryType) { + case DigitalTwinEntryType.AccountId: + case DigitalTwinEntryType.GenericContract: + entryParam.value = this.options.web3.utils.toChecksumAddress(`0x${entryParam.raw.value.substr(26)}`); + break; + case DigitalTwinEntryType.Container: + address = this.options.web3.utils.toChecksumAddress(`0x${entryParam.raw.value.substr(26)}`); + entryParam.value = new Container( + this.options, + { + accountId: this.config.accountId, + ...this.config.containerConfig, + address, + }, + ); + break; + case DigitalTwinEntryType.DigitalTwin: + address = this.options.web3.utils.toChecksumAddress(`0x${entryParam.raw.value.substr(26)}`); + entryParam.value = new DigitalTwin(this.options, { ...this.config, address }); + break; + default: + entryParam.value = entryParam.raw.value; } } } diff --git a/src/contracts/executor-agent.spec.ts b/src/contracts/executor-agent.spec.ts index 060e83c7..325a1821 100644 --- a/src/contracts/executor-agent.spec.ts +++ b/src/contracts/executor-agent.spec.ts @@ -19,18 +19,11 @@ import 'mocha'; import { expect, use } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); +import * as chaiAsPromised from 'chai-as-promised'; +import { SignerInternal } from '@evan.network/dbcp'; -import { - ContractLoader, - LogLevel, - SignerInternal, - SignerInterface, -} from '@evan.network/dbcp'; - -import { accounts } from '../test/accounts'; import { ExecutorAgent } from './executor-agent'; -import { TestUtils } from '../test/test-utils' +import { TestUtils } from '../test/test-utils'; const agentUser = '0x24Ac71311c9F9a6148944a921551686291aF5BC8'; @@ -45,13 +38,13 @@ let web3; use(chaiAsPromised); -describe.skip('Executor handler', function() { +describe.skip('Executor handler', function test() { this.timeout(300000); let executor: ExecutorAgent; before(async () => { web3 = TestUtils.getWeb3(); - const accountStore = TestUtils.getAccountStore({}); + const accountStore = TestUtils.getAccountStore(); contractLoader = await TestUtils.getContractLoader(web3); const signer = new SignerInternal({ accountStore, @@ -76,9 +69,9 @@ describe.skip('Executor handler', function() { it('should be able to create a contract', async () => { // create token for creating contract - const token = await executor.generateToken(password, [{ signature: 'Owned', }]); + const token = await executor.generateToken(password, [{ signature: 'Owned' }]); executor.token = token; - contract = await executor.createContract('Owned', [], { gas: 2000000, }); + contract = await executor.createContract('Owned', [], { gas: 2000000 }); expect(contract).not.to.be.undefined; const owner = await executor.executeContractCall(contract, 'owner'); expect(owner).to.eq(agentUser); @@ -89,10 +82,10 @@ describe.skip('Executor handler', function() { expect(owner).to.eq(agentUser); // grant tx token - const token = await executor.generateToken(password, [{ contract, functionName: 'transferOwnership', }]); + const token = await executor.generateToken(password, [{ contract, functionName: 'transferOwnership' }]); executor.token = token; // try to transfer ownership - await executor.executeContractTransaction(contract, 'transferOwnership', { gas: 2000000, }, randomAccount); + await executor.executeContractTransaction(contract, 'transferOwnership', { gas: 2000000 }, randomAccount); owner = await executor.executeContractCall(contract, 'owner'); expect(owner.toLowerCase()).to.eq(randomAccount); }); @@ -100,19 +93,19 @@ describe.skip('Executor handler', function() { describe('when managing tokens', () => { it('should allow contract creation and transactions, when token has been granted', async () => { // create token for creating contract - let token = await executor.generateToken(password, [{ signature: 'Owned', }]); + let token = await executor.generateToken(password, [{ signature: 'Owned' }]); executor.token = token; // allowed, as token has been set - const localContract = await executor.createContract('Owned', [], { gas: 2000000, }); + const localContract = await executor.createContract('Owned', [], { gas: 2000000 }); expect(localContract).not.to.be.undefined; let owner = await executor.executeContractCall(localContract, 'owner'); expect(owner).to.eq(agentUser); // grant tx token - token = await executor.generateToken(password, [{ contract: localContract, functionName: 'transferOwnership', }]); + token = await executor.generateToken(password, [{ contract: localContract, functionName: 'transferOwnership' }]); executor.token = token; // allowed, as token has been set - await executor.executeContractTransaction(localContract, 'transferOwnership', { gas: 2000000, }, randomAccount); + await executor.executeContractTransaction(localContract, 'transferOwnership', { gas: 2000000 }, randomAccount); owner = await executor.executeContractCall(localContract, 'owner'); expect(owner.toLowerCase()).to.eq(randomAccount); }); @@ -120,76 +113,77 @@ describe.skip('Executor handler', function() { it('should not allow contract creation, when token has been granted', async () => { // allowed, as token has been set delete executor.token; - const localContractPromise = executor.createContract('Owned', [], { gas: 2000000, }); + const localContractPromise = executor.createContract('Owned', [], { gas: 2000000 }); await expect(localContractPromise).to.be.rejected; }); it('should not allow transactions, when token has not been granted', async () => { // create token for creating contract - const token = await executor.generateToken(password, [{ signature: 'Owned', }]); + const token = await executor.generateToken(password, [{ signature: 'Owned' }]); executor.token = token; // allowed, as token has been set - const localContract = await executor.createContract('Owned', [], { gas: 2000000, }); + const localContract = await executor.createContract('Owned', [], { gas: 2000000 }); expect(localContract).not.to.be.undefined; const owner = await executor.executeContractCall(localContract, 'owner'); expect(owner).to.eq(agentUser); // allowed, as token has been set delete executor.token; - const txPromise = executor.executeContractTransaction(localContract, 'transferOwnership', { gas: 2000000, }, randomAccount); + const txPromise = executor.executeContractTransaction(localContract, 'transferOwnership', { gas: 2000000 }, randomAccount); await expect(txPromise).to.be.rejected; }); it('should allow multiple transactions, when matching token has been granted', async () => { // create token for creating contract - let token = await executor.generateToken(password, [{ signature: 'Owned', }]); + let token = await executor.generateToken(password, [{ signature: 'Owned' }]); executor.token = token; // allowed, as token has been set - const localContract = await executor.createContract('Owned', [], { gas: 2000000, }); + const localContract = await executor.createContract('Owned', [], { gas: 2000000 }); expect(localContract).not.to.be.undefined; const owner = await executor.executeContractCall(localContract, 'owner'); expect(owner).to.eq(agentUser); // grant tx token - token = await executor.generateToken(password, [{ contract: localContract, functionName: 'transferOwnership', count: 3, }]); + token = await executor.generateToken(password, [{ contract: localContract, functionName: 'transferOwnership', count: 3 }]); executor.token = token; // allowed (1/3), as token has been set - await executor.executeContractTransaction(localContract, 'transferOwnership', { gas: 2000000, }, agentUser); + await executor.executeContractTransaction(localContract, 'transferOwnership', { gas: 2000000 }, agentUser); // allowed (2/3), as token has been set - await executor.executeContractTransaction(localContract, 'transferOwnership', { gas: 2000000, }, agentUser); + await executor.executeContractTransaction(localContract, 'transferOwnership', { gas: 2000000 }, agentUser); // allowed (3/3), as token has been set - await executor.executeContractTransaction(localContract, 'transferOwnership', { gas: 2000000, }, agentUser); + await executor.executeContractTransaction(localContract, 'transferOwnership', { gas: 2000000 }, agentUser); // will fail - const txPromise = executor.executeContractTransaction(localContract, 'transferOwnership', { gas: 2000000, }, agentUser); + const txPromise = executor.executeContractTransaction(localContract, 'transferOwnership', { gas: 2000000 }, agentUser); await expect(txPromise).to.be.rejected; }); - it('rejects contract creations, when value has been set', async() => { + it('rejects contract creations, when value has been set', async () => { // create token for creating contract - let token = await executor.generateToken(password, [{ signature: 'Owned', }]); + const token = await executor.generateToken(password, [{ signature: 'Owned' }]); executor.token = token; // fails, as value has been given - const localContractPromise = executor.createContract('Owned', [], { gas: 2000000, value: 1000, }); + const localContractPromise = executor.createContract('Owned', [], { gas: 2000000, value: 1000 }); await expect(localContractPromise).to.be.rejected; }); - it('rejects contract transactions, when value has been set', async() => { + it('rejects contract transactions, when value has been set', async () => { // create token for creating contract - let token = await executor.generateToken(password, [{ signature: 'Owned', }]); + let token = await executor.generateToken(password, [{ signature: 'Owned' }]); executor.token = token; // allowed, as token has been set - const localContract = await executor.createContract('Owned', [], { gas: 2000000, }); + const localContract = await executor.createContract('Owned', [], { gas: 2000000 }); expect(localContract).not.to.be.undefined; - let owner = await executor.executeContractCall(localContract, 'owner'); + const owner = await executor.executeContractCall(localContract, 'owner'); expect(owner).to.eq(agentUser); // grant tx token - token = await executor.generateToken(password, [{ contract: localContract, functionName: 'transferOwnership', }]); + token = await executor.generateToken(password, [{ contract: localContract, functionName: 'transferOwnership' }]); executor.token = token; // fails, as value has been given const txPromise = executor.executeContractTransaction( - localContract, 'transferOwnership', { gas: 2000000, value: 1000, }, randomAccount); + localContract, 'transferOwnership', { gas: 2000000, value: 1000 }, randomAccount, + ); await expect(txPromise).to.be.rejected; }); }); diff --git a/src/contracts/executor-agent.ts b/src/contracts/executor-agent.ts index c13653de..269f5b9f 100644 --- a/src/contracts/executor-agent.ts +++ b/src/contracts/executor-agent.ts @@ -16,12 +16,19 @@ Fifth Floor, Boston, MA, 02110-1301 USA, or download the license from the following URL: https://evan.network/license/ */ +import * as BigNumber from 'bignumber.js'; +import * as http from 'http'; +import * as https from 'https'; +import * as url from 'url'; + +import { + ContractLoader, + EventHub, + Executor, + ExecutorOptions, + SignerInterface, +} from '@evan.network/dbcp'; -const BigNumber = require('bignumber.js'); -const http = require('http'); -const https = require('https'); -const querystring = require('querystring'); -const url = require('url'); /** * Use this function instead of request node_module to reduce browser bundle file size. @@ -29,47 +36,40 @@ const url = require('url'); * @param {any} requestOptions options including normal request node_module options * @param {Function} callback callback function */ -const request = (requestOptions: any) => { - return new Promise((resolve, reject) => { - const requestModule = requestOptions.url.startsWith('https') ? https : http; - const parsed = url.parse(requestOptions.url); - let result = ''; - - // define request options - const options = { - method: requestOptions.method || 'POST', - headers: requestOptions.header || { 'Content-Type': 'application/json' }, - hostname: parsed.hostname, - port: parsed.port, - path: parsed.path, - }; - - // start the request - const req = requestModule.request(options, (res) => { - res.on('data', (d) => result += d); - res.on('end', () => { - const resultObj = JSON.parse(result); - if (resultObj.error) { - reject(resultObj.error); - } else { - resolve(resultObj.result); - } - }); - }); +const request = (requestOptions: any) => new Promise((resolve, reject) => { + const requestModule = requestOptions.url.startsWith('https') ? https : http; + const parsed = url.parse(requestOptions.url); + let result = ''; - req.on('error', (e) => reject(e)); - req.write(JSON.stringify(requestOptions.body)); - req.end(); + // define request options + const options = { + method: requestOptions.method || 'POST', + headers: requestOptions.header || { 'Content-Type': 'application/json' }, + hostname: parsed.hostname, + port: parsed.port, + path: parsed.path, + }; + + // start the request + const req = requestModule.request(options, (res) => { + res.on('data', (d) => { + result += d; + return result; + }); + res.on('end', () => { + const resultObj = JSON.parse(result); + if (resultObj.error) { + reject(resultObj.error); + } else { + resolve(resultObj.result); + } + }); }); -}; -import { - ContractLoader, - EventHub, - Executor, - ExecutorOptions, - SignerInterface, -} from '@evan.network/dbcp'; + req.on('error', (e) => reject(e)); + req.write(JSON.stringify(requestOptions.body)); + req.end(); +}); /** @@ -88,20 +88,27 @@ export interface ExecutorAgentOptions extends ExecutorOptions { * @class ExecutorAgent (name) */ export class ExecutorAgent extends Executor { - agentUrl: string; - config: any; - contractLoader: ContractLoader; - defaultOptions: any; - eventHub: EventHub; - signer: SignerInterface; - web3: any; - token: string; + public agentUrl: string; + + public config: any; + + public contractLoader: ContractLoader; + + public defaultOptions: any; + + public eventHub: EventHub; + + public signer: SignerInterface; + + public web3: any; + + public token: string; /** - * note, that the ExecutorAgent requires the "init" function to be called when intending to use the - * EventHub helper for transactions with event return values + * note, that the ExecutorAgent requires the "init" function to be called when intending to use + * the EventHub helper for transactions with event return values */ - constructor(options: ExecutorAgentOptions) { + public constructor(options: ExecutorAgentOptions) { super(options); this.config = options.config; this.contractLoader = options.contractLoader; @@ -125,11 +132,15 @@ export class ExecutorAgent extends Executor { * .gas * @return {Promise} new contract */ - public async createContract(contractName: string, functionArguments: any[], inputOptions: any): Promise { + public async createContract( + contractName: string, + functionArguments: any[], + inputOptions: any, + ): Promise { this.log(`starting contract creation for "${contractName}" via agent`, 'debug'); if (inputOptions.value && parseInt(inputOptions.value, 10)) { - throw new Error('sending funds is not supported by the agent based executor; ' + - `value has been set to ${inputOptions.value} for new contract "${contractName}"`); + throw new Error('sending funds is not supported by the agent based executor; ' + + `value has been set to ${inputOptions.value} for new contract "${contractName}"`); } // submit to action @@ -157,14 +168,16 @@ export class ExecutorAgent extends Executor { * parameter * @return {Promise} resolves to: {Object} contract calls result */ - public async executeContractCall(contract: any, functionName: string, ...args): Promise { + public async executeContractCall(contract: any, functionName: string, ...args): Promise { this.log(`starting contract call "${functionName}" via agent`, 'debug'); // web3 compatibility for 1.2 and 2.0 let functionSignature; if (contract.options.jsonInterface) { // web3 1.2 - functionSignature = contract.options.jsonInterface.filter(fun => fun.name === functionName)[0]; + [functionSignature] = contract.options.jsonInterface.filter( + (fun) => fun.name === functionName, + ); } else { // web3 2.0 functionSignature = contract.abiModel.abi.methods[functionName].abiItem; @@ -190,10 +203,8 @@ export class ExecutorAgent extends Executor { result[index] = new BigNumber(result[index].value); } }); - } else { - if (result && result.isBigNumber) { - result = new BigNumber(result.value); - } + } else if (result && result.isBigNumber) { + result = new BigNumber(result.value); } return result; @@ -216,18 +227,21 @@ export class ExecutorAgent extends Executor { * value returned by getEventResult(eventObject) */ public async executeContractTransaction( - contract: any, functionName: string, inputOptions: any, ...functionArguments: any[]): Promise { + contract: any, functionName: string, inputOptions: any, ...functionArguments: any[] + ): Promise { this.log(`starting contract transaction "${functionName}" via agent`, 'debug'); if (inputOptions.value && parseInt(inputOptions.value, 10)) { - throw new Error('sending funds is not supported by the agent based executor; ' + - `value has been set to ${inputOptions.value} for tx "${functionName}"`); + throw new Error('sending funds is not supported by the agent based executor; ' + + `value has been set to ${inputOptions.value} for tx "${functionName}"`); } // web3 compatibility for 1.2 and 2.0 let functionSignature; if (contract.options.jsonInterface) { // web3 1.2 - functionSignature = contract.options.jsonInterface.filter(fun => fun.name === functionName)[0]; + [functionSignature] = contract.options.jsonInterface.filter( + (fun) => fun.name === functionName, + ); } else { // web3 2.0 functionSignature = contract.abiModel.abi.methods[functionName].abiItem; @@ -255,8 +269,8 @@ export class ExecutorAgent extends Executor { * value * @return {Promise} resolved when done */ - public async executeSend(inputOptions): Promise { - throw new Error(`sending funds is not supported by the agent based executor`); + public async executeSend(): Promise { + throw new Error('sending funds is not supported by the agent based executor'); } /** @@ -289,15 +303,17 @@ export class ExecutorAgent extends Executor { if (fun.functionName) { if (fun.contract.options.jsonInterface) { // web3 1.2 - newFun.signature = fun.contract.options.jsonInterface.filter(ff => ff.name === fun.functionName)[0]; + [newFun.signature] = fun.contract.options.jsonInterface.filter( + (ff) => ff.name === fun.functionName, + ); } else { // web3 2.0 - newFun.signature = fun.contract.abiModel.abi.methods[fun.functionName].abiItem; + newFun.signature = fun.contract.abiModel.abi.methods[fun.functionName].abiItem; } } } return newFun; - }) + }); return request({ url: `${this.agentUrl}/api/smart-agents/executor/generateToken`, method: 'POST', diff --git a/src/contracts/executor-wallet.spec.ts b/src/contracts/executor-wallet.spec.ts index acf08ddf..6cd84c73 100644 --- a/src/contracts/executor-wallet.spec.ts +++ b/src/contracts/executor-wallet.spec.ts @@ -19,29 +19,24 @@ import 'mocha'; import { expect, use } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); +import * as chaiAsPromised from 'chai-as-promised'; import { ContractLoader, DfsInterface, Executor, NameResolver, - SignerInternal, } from '@evan.network/dbcp'; import { accounts } from '../test/accounts'; -import { configTestcore as config } from '../config-testcore'; -import { CryptoProvider } from '../encryption/crypto-provider'; import { ExecutorWallet } from './executor-wallet'; -import { Ipfs } from '../dfs/ipfs'; -import { sampleContext, TestUtils } from '../test/test-utils'; -import { Sharing } from './sharing'; +import { TestUtils } from '../test/test-utils'; import { Wallet } from './wallet'; use(chaiAsPromised); -describe('Signer Wallet', function() { +describe('Signer Wallet', function test() { this.timeout(60000); let dfs: DfsInterface; let contractLoader: ContractLoader; @@ -65,9 +60,9 @@ describe('Signer Wallet', function() { nameResolver = await TestUtils.getNameResolver(web3); const eventHub = await TestUtils.getEventHub(web3); executor.eventHub = eventHub; - executorWallet0 = await TestUtils.getExecutorWallet(web3, wallet0, accounts[0], dfs); + executorWallet0 = await TestUtils.getExecutorWallet(web3, wallet0, accounts[0]); executorWallet0.eventHub = eventHub; - executorWallet1 = await TestUtils.getExecutorWallet(web3, wallet1, accounts[1], dfs); + executorWallet1 = await TestUtils.getExecutorWallet(web3, wallet1, accounts[1]); executorWallet1.eventHub = eventHub; }); @@ -75,12 +70,15 @@ describe('Signer Wallet', function() { it('can instantly submit transactions', async () => { // create test contract and hand over to wallet const testContract = await executor.createContract( - 'Owned', [], { from: accounts[0], gas: 200000, }); + 'Owned', [], { from: accounts[0], gas: 200000 }, + ); expect(await executor.executeContractCall(testContract, 'owner')).to.eq(accounts[0]); await executor.executeContractTransaction( - testContract, 'transferOwnership', { from: accounts[0], }, wallet0.walletAddress); + testContract, 'transferOwnership', { from: accounts[0] }, wallet0.walletAddress, + ); expect(await executor.executeContractCall( - testContract, 'owner')).to.eq(wallet0.walletAddress); + testContract, 'owner', + )).to.eq(wallet0.walletAddress); await TestUtils.nextBlock(executor, accounts[0]); await executorWallet0.executeContractTransaction( testContract, @@ -94,7 +92,8 @@ describe('Signer Wallet', function() { it('cannot submit transactions, when not in owners group', async () => { // create test contract and hand over to wallet const testContract = await executor.createContract( - 'Owned', [], { from: accounts[0], gas: 200000 }); + 'Owned', [], { from: accounts[0], gas: 200000 }, + ); expect(await executor.executeContractCall(testContract, 'owner')).to.eq(accounts[0]); await executor.executeContractTransaction( @@ -104,22 +103,27 @@ describe('Signer Wallet', function() { wallet0.walletAddress, ); expect(await executor.executeContractCall( - testContract, 'owner')).to.eq(wallet0.walletAddress); + testContract, 'owner', + )).to.eq(wallet0.walletAddress); const promise = executorWallet1.executeContractTransaction( - testContract, 'transferOwnership', { from: wallet1.walletAddress, }, accounts[1]); + testContract, 'transferOwnership', { from: wallet1.walletAddress }, accounts[1], + ); await expect(promise).to.be.rejected; }); it('can instantly submit transactions a second time', async () => { // create test contract and hand over to wallet const testContract = await executor.createContract( - 'Owned', [], { from: accounts[0], gas: 200000 }); + 'Owned', [], { from: accounts[0], gas: 200000 }, + ); expect(await executor.executeContractCall(testContract, 'owner')).to.eq(accounts[0]); await executor.executeContractTransaction( - testContract, 'transferOwnership', { from: accounts[0], }, wallet0.walletAddress); + testContract, 'transferOwnership', { from: accounts[0] }, wallet0.walletAddress, + ); expect(await executor.executeContractCall( - testContract, 'owner')).to.eq(wallet0.walletAddress); + testContract, 'owner', + )).to.eq(wallet0.walletAddress); await executorWallet0.executeContractTransaction( testContract, 'transferOwnership', @@ -132,16 +136,13 @@ describe('Signer Wallet', function() { it('can instantly submit transactions with event based result', async () => { const factoryAddress = await nameResolver.getAddress('testcontract.factory.testbc.evan'); const factory = contractLoader.loadContract('TestContractFactory', factoryAddress); - const businessCenterDomain = nameResolver.getDomainName( - config.nameResolver.domains.businessCenter); - const businessCenterAddress = '0x0000000000000000000000000000000000000000'; const data = `I like random numbers, for example: ${Math.random()}`; const contractId = await executorWallet0.executeContractTransaction( factory, 'createContract', { from: wallet0.walletAddress, - event: { target: 'TestContractFactory', eventName: 'ContractCreated', }, + event: { target: 'TestContractFactory', eventName: 'ContractCreated' }, getEventResult: (event, args) => args.newAddress, }, data, @@ -154,16 +155,13 @@ describe('Signer Wallet', function() { it('can instantly submit transactions with event based result a second time', async () => { const factoryAddress = await nameResolver.getAddress('testcontract.factory.testbc.evan'); const factory = contractLoader.loadContract('TestContractFactory', factoryAddress); - const businessCenterDomain = nameResolver.getDomainName( - config.nameResolver.domains.businessCenter); - const businessCenterAddress = '0x0000000000000000000000000000000000000000'; const data = `I like random numbers, for example: ${Math.random()}`; const contractId = await executorWallet0.executeContractTransaction( factory, 'createContract', { from: wallet0.walletAddress, - event: { target: 'TestContractFactory', eventName: 'ContractCreated', }, + event: { target: 'TestContractFactory', eventName: 'ContractCreated' }, getEventResult: (event, args) => args.newAddress, }, data, @@ -176,12 +174,15 @@ describe('Signer Wallet', function() { it('can instantly submit transactions a third time', async () => { // create test contract and hand over to wallet const testContract = await executor.createContract( - 'Owned', [], { from: accounts[0], gas: 200000 }); + 'Owned', [], { from: accounts[0], gas: 200000 }, + ); expect(await executor.executeContractCall(testContract, 'owner')).to.eq(accounts[0]); await executor.executeContractTransaction( - testContract, 'transferOwnership', { from: accounts[0], }, wallet0.walletAddress); + testContract, 'transferOwnership', { from: accounts[0] }, wallet0.walletAddress, + ); expect(await executor.executeContractCall( - testContract, 'owner')).to.eq(wallet0.walletAddress); + testContract, 'owner', + )).to.eq(wallet0.walletAddress); await executorWallet0.executeContractTransaction( testContract, @@ -195,7 +196,8 @@ describe('Signer Wallet', function() { it('can create new contracts', async () => { // create new contract const testContract = await executorWallet0.createContract( - 'Owned', [], { from: wallet0.walletAddress, autoGas: 1.1, }); + 'Owned', [], { from: wallet0.walletAddress, autoGas: 1.1 }, + ); expect(testContract.options.address).to.match(/0x[0-9a-f]{40}/ig); // current owner is wallet diff --git a/src/contracts/executor-wallet.ts b/src/contracts/executor-wallet.ts index 03ba7a69..ca5f833c 100644 --- a/src/contracts/executor-wallet.ts +++ b/src/contracts/executor-wallet.ts @@ -16,16 +16,12 @@ Fifth Floor, Boston, MA, 02110-1301 USA, or download the license from the following URL: https://evan.network/license/ */ - +import { AbiCoder } from 'web3-eth-abi'; import { ContractLoader, - EventHub, Executor, ExecutorOptions, - SignerInterface, } from '@evan.network/dbcp'; - -import { AbiCoder } from 'web3-eth-abi'; import { Wallet } from './wallet'; @@ -49,13 +45,13 @@ export interface ExecutorWalletOptions extends ExecutorOptions { * @class ExecutorWallet (name) */ export class ExecutorWallet extends Executor { - options: ExecutorWalletOptions; + public options: ExecutorWalletOptions; /** * note, that the ExecutorWallet requires the "init" function to be called when intending to use * EventHub helper for transactions with event return values */ - constructor(options: ExecutorWalletOptions) { + public constructor(options: ExecutorWalletOptions) { super(options); this.options = options; this.defaultOptions = options.defaultOptions || {}; @@ -71,21 +67,22 @@ export class ExecutorWallet extends Executor { * .gas * @return {Promise} new contract */ - async createContract(contractName: string, functionArguments: any[], inputOptions: any): - Promise { + public async createContract(contractName: string, functionArguments: any[], inputOptions: any): + Promise { this.log(`starting creation of contract "${contractName}"`, 'debug'); const compiledContract = this.options.contractLoader.getCompiledContract(contractName); if (!compiledContract || !compiledContract.bytecode) { throw new Error(`cannot find contract bytecode for contract "${contractName}"`); } // build bytecode and arguments for constructor - const input = `0x${compiledContract.bytecode}` + - this.encodeConstructorParams(JSON.parse(compiledContract.interface), functionArguments); + const input = `0x${compiledContract.bytecode}${ + this.encodeConstructorParams(JSON.parse(compiledContract.interface), functionArguments)}`; // submit tx; ContractCreated event is handled in submitRawTransaction, if target is null const txInfo = await this.options.wallet.submitRawTransaction( - null, input, Object.assign({}, inputOptions, { from: this.options.accountId })); + null, input, { ...inputOptions, from: this.options.accountId }, + ); if (!txInfo.result) { - throw new Error(`contract creation failed; txInfo: ${txInfo}`) + throw new Error(`contract creation failed; txInfo: ${txInfo}`); } else { return this.options.contractLoader.loadContract(contractName, txInfo.result); } @@ -101,31 +98,29 @@ export class ExecutorWallet extends Executor { * parameter * @return {Promise} resolves to: {Object} contract calls result */ - async executeContractCall(contract: any, functionName: string, ...args): Promise { + public async executeContractCall(contract: any, functionName: string, ...args): Promise { this.log(`starting contract call "${functionName}"`, 'debug'); if (!contract.methods[functionName]) { - throw new Error(`contract does not support method "${functionName}", ` + - `supported methods are ${Object.keys(contract.methods)}`); + throw new Error(`contract does not support method "${functionName}", ` + + `supported methods are ${Object.keys(contract.methods)}`); } if (!contract || !contract.options || !contract.options.address) { throw new Error('contract undefined or contract has no address'); } let options; - options = this.defaultOptions ? Object.assign({}, this.defaultOptions) : null; + options = this.defaultOptions ? ({ ...this.defaultOptions }) : null; if (args.length && typeof args[args.length - 1] === 'object') { // merge options, use wallet address as from options = Object.assign( options || {}, args[args.length - 1], - { from: this.options.wallet.walletAddress, }, + { from: this.options.wallet.walletAddress }, ); - return contract.methods[functionName].apply( - contract.methods, args.slice(0, args.length - 1)).call(options); - } else if (options) { - return contract.methods[functionName].apply(contract.methods, args).call(options); - } else { - return contract.methods[functionName].apply(contract.methods, args).call(); + return contract.methods[functionName](...args.slice(0, args.length - 1)).call(options); + } if (options) { + return contract.methods[functionName](...args).call(options); } + return contract.methods[functionName](...args).call(); } /** @@ -144,9 +139,10 @@ export class ExecutorWallet extends Executor { * given), the event (if event but no getEventResult was given), the * value returned by getEventResult(eventObject) */ - async executeContractTransaction( - contract: any, functionName: string, inputOptions: any, ...functionArguments: any[]): - Promise { + public async executeContractTransaction( + contract: any, functionName: string, inputOptions: any, ...functionArguments: any[] + ): + Promise { // autoGas 1.1 ==> if truthy, enables autoGas 1.1 ==> adds 10% to estimated value capped to // current block maximum minus 4* the allowed derivation per block - // The protocol allows the miner of a block @@ -156,26 +152,27 @@ export class ExecutorWallet extends Executor { this.log(`starting contract transaction "${functionName}"`, 'debug'); if (inputOptions.value && parseInt(inputOptions.value, 10)) { if (typeof inputOptions.value !== 'string') { - inputOptions.value = `0x${inputOptions.value.toString(16)}` + // eslint-disable-next-line + inputOptions.value = `0x${inputOptions.value.toString(16)}`; } } if (!this.signer) { throw new Error('signer is undefined'); } if (!contract.methods[functionName]) { - throw new Error(`contract does not support method "${functionName}", ` + - `supported methods are ${Object.keys(contract.methods)}`); + throw new Error(`contract does not support method "${functionName}", ` + + `supported methods are ${Object.keys(contract.methods)}`); } if (!contract || !contract.options || !contract.options.address) { throw new Error('contract undefined or contract has no address'); } // every argument beyond the third is an argument for the contract function - let options = Object.assign( - { timeout: 300000 }, - this.defaultOptions || {}, - inputOptions, - ); + const options = { + timeout: 300000, + ...this.defaultOptions || {}, + ...inputOptions, + }; // keep timeout before deletion const transactionTimeout = options.eventTimeout || options.timeout; @@ -187,6 +184,7 @@ export class ExecutorWallet extends Executor { const logGas = (extraParams) => { const staticEntries = { arguments: initialArguments, + // eslint-disable-next-line no-underscore-dangle contract: contract.address || contract._address, from: inputOptions.from, gasEstimated: null, @@ -198,8 +196,8 @@ export class ExecutorWallet extends Executor { }; const level = 'gasLog'; this.log(JSON.stringify(Object.assign(staticEntries, extraParams)), level); - } - return new Promise(async (resolve, reject) => { + }; + return new Promise((resolve, reject) => { // keep track of the promise state via variable as we may run into a timeout let isPending = true; let transactionHash; @@ -207,27 +205,24 @@ export class ExecutorWallet extends Executor { // timeout and event listener with this let subscription; - const stopWatching = async (isError?) => { - return new Promise((resolveStop) => { - setTimeout(() => { - if (inputOptions.event && subscription) { - if (this.eventHub) { - this.eventHub - .unsubscribe({ subscription}) - .catch((ex) => { - this.log('error occurred while unsubscribing from transaction event; ' + - `${ex.message || ex}${ex.stack || ''}`, 'error'); - }) - ; - } else { - reject('passed an event to a transaction but no event hub registered'); + const stopWatching = async (isError?) => new Promise((resolveStop) => { + setTimeout(() => { + if (inputOptions.event && subscription) { + if (this.eventHub) { + this.eventHub + .unsubscribe({ subscription }) + .catch((ex) => { + this.log('error occurred while unsubscribing from transaction event; ' + + `${ex.message || ex}${ex.stack || ''}`, 'error'); + }); + } else { + reject(new Error('passed an event to a transaction but no event hub registered')); + } } - } - isPending = false; - resolveStop(); - }, isError ? 1000 : 0); - }); - } + isPending = false; + resolveStop(); + }, isError ? 1000 : 0); + }); try { // timeout rejects promise if not already done so @@ -247,7 +242,7 @@ export class ExecutorWallet extends Executor { inputOptions.event.target, contract.options.address, inputOptions.event.eventName, - (event) => true, + () => true, (event) => { if (transactionHash === event.transactionHash) { // if we have a retriever function, use it, otherwise return entire event object @@ -261,14 +256,13 @@ export class ExecutorWallet extends Executor { // hold the evenTransaction and trigger resolve within execution callback eventResults[event.transactionHash] = event; } - } + }, ) .then((result) => { subscription = result; }) .catch((ex) => { - this.log('error occurred while subscribing to transaction event; ' + - `${ex.message || ex}; ${ex.stack || ''}`, 'error'); - }) - ; + this.log('error occurred while subscribing to transaction event; ' + + `${ex.message || ex}; ${ex.stack || ''}`, 'error'); + }); } else { this.log('passed an event to a transaction but no event hub registered', 'warning'); } @@ -278,22 +272,79 @@ export class ExecutorWallet extends Executor { functionArguments.push(options); // const estimationArguments = functionArguments.slice(); let gasEstimated; - let executeCallback; + // eslint-disable-next-line consistent-return + const executeCallback = async (err, packedResult) => { + if (err) { + return reject(new Error(`${functionName} failed: ${err}`)); + } + const { receipt } = packedResult; + try { + // keep transaction hash for checking agains it in event + transactionHash = receipt && receipt.transactionHash ? receipt.transactionHash : ''; + let optionsGas; + if (typeof options.gas === 'string' && options.gas.startsWith('0x')) { + optionsGas = parseInt(options.gas, 16); + } else { + optionsGas = parseInt(options.gas, 10); + } + if (optionsGas !== receipt.gasUsed) { + logGas({ + status: 'success', + gasUsed: receipt.gasUsed, + gasEstimated, + transactionHash, + }); + // if no event to watch for was given, resolve promise here + if (!inputOptions.event || !this.eventHub) { + isPending = false; + resolve(); + } else if (eventResults[transactionHash]) { + await stopWatching(); + if (inputOptions.getEventResult) { + resolve(inputOptions.getEventResult( + eventResults[transactionHash], + eventResults[transactionHash].args + || eventResults[transactionHash].returnValues, + )); + } else { + resolve(eventResults[transactionHash]); + } + } + } else { + const errorText = 'all gas used up'; + this.log(`${functionName} failed: ${errorText}`, 'error'); + if (inputOptions.event && this.eventHub) { + await stopWatching(true); + } + logGas({ + status: 'error', + message: 'transaction failed', + gasUsed: receipt.gasUsed, + gasEstimated, + transactionHash, + }); + reject(errorText); + } + } catch (ex) { + return reject(new Error(`${functionName} failed: ${ex.message}`)); + } + }; + const estimationCallback = async (estimationError, gasAmount) => { gasEstimated = gasAmount; if (estimationError) { await stopWatching(true); logGas({ status: 'error', message: `could not estimate; ${estimationError}` }); - reject(`could not estimate gas usage for ${functionName}: ${estimationError}; ` + - estimationError.stack); + reject(new Error(`could not estimate gas usage for ${functionName}: ${estimationError}; ${ + estimationError.stack}`)); } else if (inputOptions.estimate) { await stopWatching(); resolve(gasAmount); - } else if (!inputOptions.force && - parseInt(inputOptions.gas, 10) === parseInt(gasAmount, 10)) { + } else if (!inputOptions.force + && parseInt(inputOptions.gas, 10) === parseInt(gasAmount, 10)) { await stopWatching(true); logGas({ status: 'error', message: 'out of gas estimated' }); - reject(`transaction ${functionName} by ${options.from} would most likely fail`); + reject(new Error(`transaction ${functionName} by ${options.from} would most likely fail`)); } else { // execute contract function // recover original from, as estimate converts from to lower case @@ -304,94 +355,25 @@ export class ExecutorWallet extends Executor { contract, functionName, functionArguments.slice(0, -1), - Object.assign({}, options), + { ...options }, (...args) => { executeCallback.apply(this, args).catch((ex) => { reject(ex); }); }, ); } }; - executeCallback = async (err, packedResult) => { - if (err) { - return reject(`${functionName} failed: ${err}`); - } - const { receipt } = packedResult; - try { - // keep transaction hash for checking agains it in event - transactionHash = receipt && receipt.transactionHash ? receipt.transactionHash : ''; - if (err) { - this.log(`${functionName} failed: ${err.message || err}`, 'error'); - logGas({ - status: 'error', - message: 'transaction submit error', - gasEstimated, - transactionHash, - }); - reject(err); - } else { - let optionsGas; - if (typeof options.gas === 'string' && options.gas.startsWith('0x')) { - optionsGas = parseInt(options.gas, 16); - } else { - optionsGas = parseInt(options.gas, 10); - } - if (optionsGas !== receipt.gasUsed) { - logGas({ - status: 'success', - gasUsed: receipt.gasUsed, - gasEstimated, - transactionHash, - }); - // if no event to watch for was given, resolve promise here - if (!inputOptions.event || !this.eventHub) { - isPending = false; - resolve(); - } else if (eventResults[transactionHash]) { - await stopWatching(); - if (inputOptions.getEventResult) { - resolve(inputOptions.getEventResult( - eventResults[transactionHash], - eventResults[transactionHash].args || - eventResults[transactionHash].returnValues - )); - } else { - resolve(eventResults[transactionHash]); - } - } - } else { - const errorText = 'all gas used up'; - this.log(`${functionName} failed: ${errorText}`, 'error'); - if (inputOptions.event && this.eventHub) { - await stopWatching(true); - } - logGas({ - status: 'error', - message: 'transaction failed', - gasUsed: receipt.gasUsed, - gasEstimated, - transactionHash, - }); - reject(errorText); - } - } - } catch (ex) { - return reject(`${functionName} failed: ${ex.message}`); - } - }; // estimate tx with wallet accountid instead of users account id - const estimateOptions = Object.assign( - {}, options, { from: this.options.wallet.walletAddress, }); - contract.methods[functionName] - .apply(contract.methods, initialArguments) + const estimateOptions = { ...options, from: this.options.wallet.walletAddress }; + contract.methods[functionName](...initialArguments) .estimateGas( estimateOptions, (...args) => { estimationCallback.apply(this, args).catch((ex) => { reject(ex); }); }, - ) - ; + ); } catch (ex) { this.log(`${functionName} failed: ${ex.message}`, 'error'); - await stopWatching(true); - logGas({ status: 'error', message: 'transaction could not be started' }); - reject(ex); + stopWatching(true).then(() => { + logGas({ status: 'error', message: 'transaction could not be started' }); + reject(ex); + }); } }); } @@ -403,8 +385,8 @@ export class ExecutorWallet extends Executor { * value * @return {Promise} resolved when done */ - async executeSend(inputOptions): Promise { - throw new Error(`sending funds is not supported by the walled based executor`); + public async executeSend(): Promise { + throw new Error('sending funds is not supported by the walled based executor'); } /** @@ -419,14 +401,12 @@ export class ExecutorWallet extends Executor { private encodeConstructorParams(abi: any[], params: any[]) { if (params.length) { return abi - .filter(json => json.type === 'constructor' && json.inputs.length === params.length) - .map(json => json.inputs.map(input => input.type)) - .map(types => coder.encodeParameters(types, params)) - .map(encodedParams => encodedParams.replace(/^0x/, ''))[0] || '' - ; - } else { - return ''; + .filter((json) => json.type === 'constructor' && json.inputs.length === params.length) + .map((json) => json.inputs.map((input) => input.type)) + .map((types) => coder.encodeParameters(types, params)) + .map((encodedParams) => encodedParams.replace(/^0x/, ''))[0] || ''; } + return ''; } /** @@ -441,20 +421,22 @@ export class ExecutorWallet extends Executor { * @param {function} handleTxResult callback(error, result) * @return {void} */ - private signAndExecuteTransactionViaWallet ( - contract, functionName, functionArguments, options, handleTxResult): void { - this.log(`using wallet ${this.options.wallet.walletAddress} ` + - `for making transaction "${functionName}"`, 'debug'); + private signAndExecuteTransactionViaWallet( + contract, functionName, functionArguments, options, handleTxResult, + ): void { + this.log(`using wallet ${this.options.wallet.walletAddress} ` + + `for making transaction "${functionName}"`, 'debug'); this.options.wallet .submitTransaction( contract, functionName, - Object.assign({}, options, { from: this.options.accountId }), + { ...options, from: this.options.accountId }, ...functionArguments, ) - .then((result) => Promise.all([this.options.web3.eth.getTransactionReceipt(result.event.transactionHash), result])) + .then((result) => Promise.all( + [this.options.web3.eth.getTransactionReceipt(result.event.transactionHash), result], + )) .then(([receipt, result]) => { handleTxResult(null, { receipt, ...result }); }) - .catch((error) => { handleTxResult(error); }) - ; + .catch((error) => { handleTxResult(error); }); } } diff --git a/src/contracts/rights-and-roles.spec.ts b/src/contracts/rights-and-roles.spec.ts index 4b2174f5..f37fdcc8 100644 --- a/src/contracts/rights-and-roles.spec.ts +++ b/src/contracts/rights-and-roles.spec.ts @@ -18,29 +18,20 @@ */ import 'mocha'; -import * as Throttle from 'promise-parallel-throttle'; -import chaiAsPromised = require('chai-as-promised'); +import * as chaiAsPromised from 'chai-as-promised'; import { expect, use } from 'chai'; -import { - ContractLoader, - Executor, - NameResolver, -} from '@evan.network/dbcp'; - import { accounts } from '../test/accounts'; import { configTestcore as config } from '../config-testcore'; import { DataContract } from './data-contract/data-contract'; import { RightsAndRoles, ModificationType, PropertyType } from './rights-and-roles'; -import { ServiceContract } from './service-contract/service-contract'; -import { TestUtils } from '../test/test-utils' +import { TestUtils } from '../test/test-utils'; use(chaiAsPromised); -describe('Rights and Roles handler', function() { +describe('Rights and Roles handler', function test() { this.timeout(300000); - let sc: ServiceContract; let executor; let rar: RightsAndRoles; let businessCenterDomain; @@ -55,53 +46,67 @@ describe('Rights and Roles handler', function() { web3 = TestUtils.getWeb3(); executor = await TestUtils.getExecutor(web3); rar = await TestUtils.getRightsAndRoles(web3); - sc = await TestUtils.getServiceContract(web3, ipfs); const loader = await TestUtils.getContractLoader(web3); const nameResolver = await TestUtils.getNameResolver(web3); businessCenterDomain = nameResolver.getDomainName(config.nameResolver.domains.businessCenter); const businessCenterAddress = await nameResolver.getAddress(businessCenterDomain); businessCenter = await loader.loadContract('BusinessCenter', businessCenterAddress); if (!await executor.executeContractCall( - businessCenter, 'isMember', accounts[0], { from: accounts[0], })) { + businessCenter, 'isMember', accounts[0], { from: accounts[0] }, + ) + ) { await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[0], autoGas: 1.1, }); + businessCenter, 'join', { from: accounts[0], autoGas: 1.1 }, + ); } if (!await executor.executeContractCall( - businessCenter, 'isMember', accounts[1], { from: accounts[1], })) { + businessCenter, 'isMember', accounts[1], { from: accounts[1] }, + ) + ) { await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[1], autoGas: 1.1, }); + businessCenter, 'join', { from: accounts[1], autoGas: 1.1 }, + ); } if (!await executor.executeContractCall( - businessCenter, 'isMember', accounts[2], { from: accounts[2], })) { + businessCenter, 'isMember', accounts[2], { from: accounts[2] }, + ) + ) { await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[2], autoGas: 1.1, }); + businessCenter, 'join', { from: accounts[2], autoGas: 1.1 }, + ); } dc = await TestUtils.getDataContract(web3, ipfs); sharing = await TestUtils.getSharing(web3, ipfs); }); it('should be able to retrieve all members', async () => { - const contract = await dc.create('testdatacontract', accounts[0], businessCenterDomain); - await dc.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[1]); - await dc.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[2]); - const contractParticipants = await rar.getMembers(contract); - const members = contractParticipants[1]; - expect(members.length).to.eq(3); - expect(members[0]).to.eq(accounts[0]); - expect(members[1]).to.eq(accounts[1]); - expect(members[2]).to.eq(accounts[2]); + const contract = await dc.create('testdatacontract', accounts[0], businessCenterDomain); + await dc.inviteToContract( + businessCenterDomain, contract.options.address, accounts[0], accounts[1], + ); + await dc.inviteToContract( + businessCenterDomain, contract.options.address, accounts[0], accounts[2], + ); + const contractParticipants = await rar.getMembers(contract); + const members = contractParticipants[1]; + expect(members.length).to.eq(3); + expect(members[0]).to.eq(accounts[0]); + expect(members[1]).to.eq(accounts[1]); + expect(members[2]).to.eq(accounts[2]); }); it('should be able to retrieve more than 10 members per role', async () => { - const contract = await dc.create('testdatacontract', accounts[0], null); - const invitees = [...Array(31)].map(() => TestUtils.getRandomAddress()); - await Promise.all(invitees.map(invitee => - dc.inviteToContract(null, contract.options.address, accounts[0], invitee))); - const contractParticipants = await rar.getMembers(contract); - const membersResult = contractParticipants[1].sort(); - expect(membersResult).to.deep.eq([accounts[0], ...invitees].sort()); + const contract = await dc.create('testdatacontract', accounts[0], null); + const invitees = [...Array(31)].map(() => TestUtils.getRandomAddress()); + await Promise.all(invitees.map((invitee) => dc.inviteToContract( + null, + contract.options.address, + accounts[0], + invitee, + ))); + const contractParticipants = await rar.getMembers(contract); + const membersResult = contractParticipants[1].sort(); + expect(membersResult).to.deep.eq([accounts[0], ...invitees].sort()); }); it('should be able to retrieve members from a business center', async () => { @@ -109,7 +114,7 @@ describe('Rights and Roles handler', function() { expect(Object.keys(bcMembers).length).to.eq(5); }); - describe('when updating permissions', async() => { + describe('when updating permissions', async () => { const samples = [ '0x0000000000000000000000000000000000000000000000000000000000000001', '0x0000000000000000000000000000000000000000000000000000000000000002', @@ -121,17 +126,20 @@ describe('Rights and Roles handler', function() { // create sample contract, invite second user, add sharing for this user const contract = await dc.create('testdatacontract', accounts[0], businessCenterDomain); await dc.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[1]); + businessCenterDomain, contract.options.address, accounts[0], accounts[1], + ); const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', blockNr); await sharing.addSharing( - contract.options.address, accounts[0], accounts[1], '*', blockNr, contentKey); + contract.options.address, accounts[0], accounts[1], '*', blockNr, contentKey, + ); return contract; } it('should be able to grant operation permissions to an existing contract', async () => { const contract = await createSampleContract(); await expect(dc.addListEntries( - contract, 'new_entry', [samples[0]], accounts[1])).to.be.rejected; + contract, 'new_entry', [samples[0]], accounts[1], + )).to.be.rejected; await rar.setOperationPermission( contract, accounts[0], @@ -142,13 +150,15 @@ describe('Rights and Roles handler', function() { true, ); await expect(dc.addListEntries( - contract, 'new_entry', [samples[0]], accounts[1])).to.be.fulfilled; + contract, 'new_entry', [samples[0]], accounts[1], + )).to.be.fulfilled; }); it('should be able to revoke operation permissions to an existing contract', async () => { const contract = await createSampleContract(); await expect(dc.addListEntries( - contract, 'list_settable_by_member', [samples[0]], accounts[1])).to.be.fulfilled; + contract, 'list_settable_by_member', [samples[0]], accounts[1], + )).to.be.fulfilled; await rar.setOperationPermission( contract, accounts[0], @@ -159,7 +169,8 @@ describe('Rights and Roles handler', function() { false, ); await expect(dc.addListEntries( - contract, 'list_settable_by_member', [samples[0]], accounts[1])).to.be.rejected; + contract, 'list_settable_by_member', [samples[0]], accounts[1], + )).to.be.rejected; }); it('should be able to grant function permissions to an existing contract', async () => { @@ -168,10 +179,12 @@ describe('Rights and Roles handler', function() { await dc.addListEntries(contract, 'list_settable_by_member', [samples[1]], accounts[1]); await dc.addListEntries(contract, 'list_settable_by_member', [samples[2]], accounts[1]); await expect(dc.removeListEntry( - contract, 'list_settable_by_member', 2, accounts[1])).to.be.rejected; + contract, 'list_settable_by_member', 2, accounts[1], + )).to.be.rejected; await rar.setFunctionPermission( - contract, accounts[0], memberRole, 'removeListEntry(bytes32,uint256)', true) + contract, accounts[0], memberRole, 'removeListEntry(bytes32,uint256)', true, + ); await rar.setOperationPermission( contract, accounts[0], @@ -182,18 +195,22 @@ describe('Rights and Roles handler', function() { true, ); await expect(dc.removeListEntry( - contract, 'list_settable_by_member', 2, accounts[1])).to.be.fulfilled; + contract, 'list_settable_by_member', 2, accounts[1], + )).to.be.fulfilled; }); it('should be able to revoke function permissions to an existing contract', async () => { const contract = await createSampleContract(); await expect(dc.addListEntries( - contract, 'list_settable_by_member', [samples[0]], accounts[1])).to.be.fulfilled; + contract, 'list_settable_by_member', [samples[0]], accounts[1], + )).to.be.fulfilled; await rar.setFunctionPermission( - contract, accounts[0], memberRole, 'addListEntries(bytes32[],bytes32[])', false); + contract, accounts[0], memberRole, 'addListEntries(bytes32[],bytes32[])', false, + ); await expect(dc.addListEntries( - contract, 'list_settable_by_member', [samples[1]], accounts[1])).to.be.rejected; + contract, 'list_settable_by_member', [samples[1]], accounts[1], + )).to.be.rejected; }); @@ -210,6 +227,7 @@ describe('Rights and Roles handler', function() { await rar.removeAccountFromRole(contract, accounts[1], accounts[0], 0); contractParticipants = await rar.getMembers(contract); + // eslint-disable-next-line owners = contractParticipants[0]; expect(owners.length).to.eq(1); expect(owners[0]).to.eq(accounts[1]); @@ -219,7 +237,8 @@ describe('Rights and Roles handler', function() { await rar.removeAccountFromRole(contract, accounts[0], accounts[1], 0); contractParticipants = await rar.getMembers(contract); - owners = contractParticipants[0]; + + owners = contractParticipants['0']; expect(owners.length).to.eq(1); expect(owners[0]).to.eq(accounts[0]); @@ -228,6 +247,7 @@ describe('Rights and Roles handler', function() { await rar.removeAccountFromRole(contract, accounts[1], accounts[0], 0); contractParticipants = await rar.getMembers(contract); + // eslint-disable-next-line owners = contractParticipants[0]; expect(owners.length).to.eq(1); expect(owners[0]).to.eq(accounts[1]); diff --git a/src/contracts/rights-and-roles.ts b/src/contracts/rights-and-roles.ts index 1fa0700c..adbad715 100644 --- a/src/contracts/rights-and-roles.ts +++ b/src/contracts/rights-and-roles.ts @@ -17,8 +17,6 @@ the following URL: https://evan.network/license/ */ -import prottle = require('prottle'); - import { ContractLoader, Executor, @@ -27,6 +25,8 @@ import { LoggerOptions, } from '@evan.network/dbcp'; +import prottle = require('prottle'); + const simultaneousRolesProcessed = 2; @@ -34,16 +34,20 @@ const simultaneousRolesProcessed = 2; * type of modification in contraction */ export enum ModificationType { - Set = '0xd2f67e6aeaad1ab7487a680eb9d3363a597afa7a3de33fa9bf3ae6edcb88435d', // web3.sha3('set') - Remove = '0x8dd27a19ebb249760a6490a8d33442a54b5c3c8504068964b74388bfe83458be', // web3.sha3('remove') + /** web3.sha3('set') */ + Set = '0xd2f67e6aeaad1ab7487a680eb9d3363a597afa7a3de33fa9bf3ae6edcb88435d', + /** web3.sha3('remove') */ + Remove = '0x8dd27a19ebb249760a6490a8d33442a54b5c3c8504068964b74388bfe83458be', } /** * property to set in contract */ export enum PropertyType { - Entry = '0x84f3db82fb6cd291ed32c6f64f7f5eda656bda516d17c6bc146631a1f05a1833', // web3.sha3('entry') - ListEntry = '0x7da2a80303fd8a8b312bb0f3403e22702ece25aa85a5e213371a770a74a50106', // web3.sha3('listentry') + /** web3.sha3('entry') */ + Entry = '0x84f3db82fb6cd291ed32c6f64f7f5eda656bda516d17c6bc146631a1f05a1833', + /** web3.sha3('listentry') */ + ListEntry = '0x7da2a80303fd8a8b312bb0f3403e22702ece25aa85a5e213371a770a74a50106', } export interface RightsAndRolesOptions extends LoggerOptions { @@ -59,11 +63,11 @@ export interface RightsAndRolesOptions extends LoggerOptions { * @class RightsAndRoles (name) */ export class RightsAndRoles extends Logger { - options: RightsAndRolesOptions; + public options: RightsAndRolesOptions; - constructor(options: RightsAndRolesOptions) { + public constructor(options: RightsAndRolesOptions) { super(options); - this.options = Object.assign({}, options); + this.options = { ...options }; } /** @@ -74,12 +78,18 @@ export class RightsAndRoles extends Logger { * @param {string} accountId account id to check call for * @param {string} hash hash from `getOperationCapabilityHash` */ - public async canCallOperation(contract: string|any, accountId: string, hash: string): Promise { + public async canCallOperation( + contract: string|any, + accountId: string, + hash: string, + ): Promise { const contractId = typeof contract === 'string' ? contract : contract.options.address; const auth = this.options.contractLoader.loadContract('DSAuth', contractId); const dsRolesAddress = await this.options.executor.executeContractCall(auth, 'authority'); - const dsRolesContract = this.options.contractLoader.loadContract('DSRolesPerContract', dsRolesAddress); - return await this.options.executor.executeContractCall( + const dsRolesContract = this.options.contractLoader.loadContract( + 'DSRolesPerContract', dsRolesAddress, + ); + return this.options.executor.executeContractCall( dsRolesContract, 'canCallOperation', accountId, @@ -96,24 +106,26 @@ export class RightsAndRoles extends Logger { */ public async getMembers(contract: any|string): Promise { const result = {}; - const contractInstance = (typeof contract === 'object') ? - contract : this.options.contractLoader.loadContract('BaseContractInterface', contract); + const contractInstance = (typeof contract === 'object') + ? contract : this.options.contractLoader.loadContract('BaseContractInterface', contract); const rolesAddress = await this.options.executor.executeContractCall(contractInstance, 'authority'); const rolesContract = this.options.contractLoader.loadContract('DSRolesPerContract', rolesAddress); const roleCount = await this.options.executor.executeContractCall(rolesContract, 'roleCount'); // array of functions that retrieve an element as a promise const retrievals = [...Array(parseInt(roleCount, 10))].map( - (_, role) => role).reverse().map(role => async () => { - const count = parseInt( - await this.options.executor.executeContractCall(rolesContract, 'role2userCount', role), - 10); - result[role] = await this.options.nameResolver.getArrayFromUintMapping( - rolesContract, - () => this.options.executor.executeContractCall(rolesContract, 'role2userCount', role), - (i) => this.options.executor.executeContractCall(rolesContract, 'role2index2user', role, i + 1), - count, - ); - }); + (_, role) => role, + ).reverse().map((role) => async () => { + const count = parseInt( + await this.options.executor.executeContractCall(rolesContract, 'role2userCount', role), + 10, + ); + result[role] = await this.options.nameResolver.getArrayFromUintMapping( + rolesContract, + () => this.options.executor.executeContractCall(rolesContract, 'role2userCount', role), + (i) => this.options.executor.executeContractCall(rolesContract, 'role2index2user', role, i + 1), + count, + ); + }); // run these function windowed, chain .then()s, return result array await prottle(simultaneousRolesProcessed, retrievals); return result; @@ -148,17 +160,23 @@ export class RightsAndRoles extends Logger { * @return {Promise} resolved when done */ public async setFunctionPermission( - contract: string|any, accountId: string, role: number, functionSignature: string, allow: boolean) { + contract: string|any, + accountId: string, + role: number, + functionSignature: string, + allow: boolean, + ) { const contractId = typeof contract === 'string' ? contract : contract.options.address; const auth = this.options.contractLoader.loadContract('DSAuth', contractId); const dsRolesAddress = await this.options.executor.executeContractCall(auth, 'authority'); const dsRolesContract = this.options.contractLoader.loadContract('DSRolesPerContract', dsRolesAddress); const keccak256 = this.options.web3.utils.soliditySha3; - const bytes4 = input => input.substr(0, 10); + const bytes4 = (input) => input.substr(0, 10); const permissionHash = bytes4(keccak256(functionSignature)); await this.options.executor.executeContractTransaction( - dsRolesContract, 'setRoleCapability', { from: accountId, autoGas: 1.1, }, - role, '0x0000000000000000000000000000000000000000', permissionHash, allow); + dsRolesContract, 'setRoleCapability', { from: accountId, autoGas: 1.1 }, + role, '0x0000000000000000000000000000000000000000', permissionHash, allow, + ); } /** @@ -174,22 +192,27 @@ export class RightsAndRoles extends Logger { * @return {Promise} resolved when done */ public async setOperationPermission( - contract: string|any, - accountId: string, - role: number, - propertyName: string, - propertyType: PropertyType, - modificationType: ModificationType, - allow: boolean) { + contract: string|any, + accountId: string, + role: number, + propertyName: string, + propertyType: PropertyType, + modificationType: ModificationType, + allow: boolean, + ) { const contractId = typeof contract === 'string' ? contract : contract.options.address; const auth = this.options.contractLoader.loadContract('DSAuth', contractId); const dsRolesAddress = await this.options.executor.executeContractCall(auth, 'authority'); const dsRolesContract = this.options.contractLoader.loadContract('DSRolesPerContract', dsRolesAddress); const keccak256 = this.options.web3.utils.soliditySha3; - const permissionHash = keccak256(keccak256(propertyType, keccak256(propertyName)), modificationType) + const permissionHash = keccak256( + keccak256(propertyType, keccak256(propertyName)), + modificationType, + ); await this.options.executor.executeContractTransaction( - dsRolesContract, 'setRoleOperationCapability', { from: accountId, autoGas: 1.1, }, - role, '0x0000000000000000000000000000000000000000', permissionHash, allow); + dsRolesContract, 'setRoleOperationCapability', { from: accountId, autoGas: 1.1 }, + role, '0x0000000000000000000000000000000000000000', permissionHash, allow, + ); } /** @@ -202,17 +225,19 @@ export class RightsAndRoles extends Logger { * @return {Promise} resolved when done */ public async addAccountToRole( - contract: string|any, - accountId: string, - targetAccountId: string, - role: number) { + contract: string|any, + accountId: string, + targetAccountId: string, + role: number, + ) { const contractId = typeof contract === 'string' ? contract : contract.options.address; const auth = this.options.contractLoader.loadContract('DSAuth', contractId); const dsRolesAddress = await this.options.executor.executeContractCall(auth, 'authority'); const dsRolesContract = this.options.contractLoader.loadContract('DSRolesPerContract', dsRolesAddress); await this.options.executor.executeContractTransaction( - dsRolesContract, 'setUserRole', { from: accountId, autoGas: 1.1, }, - targetAccountId, role, true); + dsRolesContract, 'setUserRole', { from: accountId, autoGas: 1.1 }, + targetAccountId, role, true, + ); } /** @@ -226,15 +251,16 @@ export class RightsAndRoles extends Logger { * @return {Promise} true is given user as specified role */ public async hasUserRole( - contract: string|any, - accountId: string, - targetAccountId: string, - role: number) { + contract: string|any, + accountId: string, + targetAccountId: string, + role: number, + ) { const contractId = typeof contract === 'string' ? contract : contract.options.address; const auth = this.options.contractLoader.loadContract('DSAuth', contractId); const dsRolesAddress = await this.options.executor.executeContractCall(auth, 'authority'); const dsRolesContract = this.options.contractLoader.loadContract('DSRolesPerContract', dsRolesAddress); - return this.options.executor.executeContractCall(dsRolesContract, 'hasUserRole', targetAccountId, role) + return this.options.executor.executeContractCall(dsRolesContract, 'hasUserRole', targetAccountId, role); } /** @@ -247,17 +273,19 @@ export class RightsAndRoles extends Logger { * @return {Promise} resolved when done */ public async removeAccountFromRole( - contract: string|any, - accountId: string, - targetAccountId: string, - role: number) { + contract: string|any, + accountId: string, + targetAccountId: string, + role: number, + ) { const contractId = typeof contract === 'string' ? contract : contract.options.address; const auth = this.options.contractLoader.loadContract('DSAuth', contractId); const dsRolesAddress = await this.options.executor.executeContractCall(auth, 'authority'); const dsRolesContract = this.options.contractLoader.loadContract('DSRolesPerContract', dsRolesAddress); await this.options.executor.executeContractTransaction( - dsRolesContract, 'setUserRole', { from: accountId, autoGas: 1.1, }, - targetAccountId, role, false); + dsRolesContract, 'setUserRole', { from: accountId, autoGas: 1.1 }, + targetAccountId, role, false, + ); } /** @@ -268,15 +296,20 @@ export class RightsAndRoles extends Logger { * @param {string} targetAccountId target accountId * @return {Promise} resolved when done */ - public async transferOwnership(contract: string|any, accountId: string, targetAccountId: string - ): Promise { + public async transferOwnership( + contract: string|any, + accountId: string, + targetAccountId: string, + ): Promise { const contractId = typeof contract === 'string' ? contract : contract.options.address; const auth = this.options.contractLoader.loadContract('DSAuth', contractId); const dsRolesAddress = await this.options.executor.executeContractCall(auth, 'authority'); const dsRolesContract = this.options.contractLoader.loadContract('DSRolesPerContract', dsRolesAddress); await this.options.executor.executeContractTransaction( - auth, 'setOwner', { from: accountId, autoGas: 1.1, }, targetAccountId); + auth, 'setOwner', { from: accountId, autoGas: 1.1 }, targetAccountId, + ); await this.options.executor.executeContractTransaction( - dsRolesContract, 'setOwner', { from: accountId, autoGas: 1.1, }, targetAccountId); + dsRolesContract, 'setOwner', { from: accountId, autoGas: 1.1 }, targetAccountId, + ); } } diff --git a/src/contracts/service-contract/service-contract.spec.ts b/src/contracts/service-contract/service-contract.spec.ts index e4c20ecc..44ac5fb8 100644 --- a/src/contracts/service-contract/service-contract.spec.ts +++ b/src/contracts/service-contract/service-contract.spec.ts @@ -19,18 +19,16 @@ import 'mocha'; import { expect, use } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); +import * as chaiAsPromised from 'chai-as-promised'; import { ContractLoader, - EventHub, Executor, KeyProvider, NameResolver, } from '@evan.network/dbcp'; import { accounts } from '../../test/accounts'; -import { ContractState } from '../base-contract/base-contract'; import { Ipfs } from '../../dfs/ipfs'; import { ServiceContract } from './service-contract'; import { configTestcore as config } from '../../config-testcore'; @@ -39,12 +37,11 @@ import { TestUtils } from '../../test/test-utils'; use(chaiAsPromised); -describe('ServiceContract', function() { +describe('ServiceContract', function test() { this.timeout(600000); let sc0: ServiceContract; let sc1: ServiceContract; let sc2: ServiceContract; - let contractFactory: any; let executor: Executor; let loader: ContractLoader; let businessCenterDomain; @@ -62,24 +59,24 @@ describe('ServiceContract', function() { type: 'object', additionalProperties: false, properties: { - author: { type: 'string', }, - privateData: { type: 'object', }, + author: { type: 'string' }, + privateData: { type: 'object' }, }, }, payload: { type: 'object', additionalProperties: false, - required: [ 'callName', ], + required: ['callName'], properties: { - callName: { type: 'string', }, - tags: { type: 'string', }, - endDate: { type: 'integer', }, - allowMultipleAnswers: { type: 'boolean', }, - amount: { type: 'integer', }, - articleNumber: { type: 'string', }, - possibleWeek: { type: 'integer', }, - note: { type: 'string', }, - privateData: { type: 'object', }, + callName: { type: 'string' }, + tags: { type: 'string' }, + endDate: { type: 'integer' }, + allowMultipleAnswers: { type: 'boolean' }, + amount: { type: 'integer' }, + articleNumber: { type: 'string' }, + possibleWeek: { type: 'integer' }, + note: { type: 'string' }, + privateData: { type: 'object' }, }, }, }, @@ -92,17 +89,17 @@ describe('ServiceContract', function() { type: 'object', additionalProperties: false, properties: { - author: { type: 'string', }, + author: { type: 'string' }, }, }, payload: { type: 'object', additionalProperties: false, properties: { - possibleAmount: { type: 'integer', }, - price: { type: 'integer', }, - possibleDeliveryWeek: { type: 'integer', }, - note: { type: 'string', }, + possibleAmount: { type: 'integer' }, + price: { type: 'integer' }, + possibleDeliveryWeek: { type: 'integer' }, + note: { type: 'string' }, }, }, }, @@ -118,24 +115,24 @@ describe('ServiceContract', function() { type: 'object', additionalProperties: false, properties: { - author: { type: 'string', }, + author: { type: 'string' }, }, }, payload: { type: 'object', additionalProperties: false, - required: [ 'callName', ], + required: ['callName'], properties: { - callName: { type: 'string', }, - tags: { type: 'string', }, - endDate: { type: 'integer', }, - allowMultipleAnswers: { type: 'boolean', }, - amount: { type: 'integer', }, - articleNumber: { type: 'string', }, - possibleWeek: { type: 'integer', }, - note: { type: 'string', }, + callName: { type: 'string' }, + tags: { type: 'string' }, + endDate: { type: 'integer' }, + allowMultipleAnswers: { type: 'boolean' }, + amount: { type: 'integer' }, + articleNumber: { type: 'string' }, + possibleWeek: { type: 'integer' }, + note: { type: 'string' }, }, - } + }, }, }, responseParameters: { @@ -146,22 +143,22 @@ describe('ServiceContract', function() { type: 'object', additionalProperties: false, properties: { - author: { type: 'string', }, + author: { type: 'string' }, }, }, payload: { type: 'object', additionalProperties: false, properties: { - possibleAmount: { type: 'integer', }, - price: { type: 'integer', }, - possibleDeliveryWeek: { type: 'integer', }, - note: { type: 'string', }, + possibleAmount: { type: 'integer' }, + price: { type: 'integer' }, + possibleDeliveryWeek: { type: 'integer' }, + note: { type: 'string' }, }, }, }, }, - } + }; const sampleCall = { metadata: { author: accounts[0], @@ -214,7 +211,7 @@ describe('ServiceContract', function() { cryptoProvider: TestUtils.getCryptoProvider(), dfs: ipfs, executor, - keyProvider: new KeyProvider({ keys: keys0, }), + keyProvider: new KeyProvider({ keys: keys0 }), loader, nameResolver, sharing: await TestUtils.getSharing(web3, ipfs), @@ -233,7 +230,7 @@ describe('ServiceContract', function() { cryptoProvider: TestUtils.getCryptoProvider(), dfs: ipfs, executor, - keyProvider: new KeyProvider({ keys: keys1, }), + keyProvider: new KeyProvider({ keys: keys1 }), loader, nameResolver, sharing: await TestUtils.getSharing(web3, ipfs), @@ -255,7 +252,7 @@ describe('ServiceContract', function() { cryptoProvider: TestUtils.getCryptoProvider(), dfs: ipfs, executor, - keyProvider: new KeyProvider({ keys: keys2, }), + keyProvider: new KeyProvider({ keys: keys2 }), loader, nameResolver, sharing: await TestUtils.getSharing(web3, ipfs), @@ -267,19 +264,28 @@ describe('ServiceContract', function() { const businessCenterAddress = await nameResolver.getAddress(businessCenterDomain); const businessCenter = await loader.loadContract('BusinessCenter', businessCenterAddress); if (!await executor.executeContractCall( - businessCenter, 'isMember', accounts[0], { from: accounts[0], })) { + businessCenter, 'isMember', accounts[0], { from: accounts[0] }, + ) + ) { await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[0], autoGas: 1.1, }); + businessCenter, 'join', { from: accounts[0], autoGas: 1.1 }, + ); } if (!await executor.executeContractCall( - businessCenter, 'isMember', accounts[1], { from: accounts[1], })) { + businessCenter, 'isMember', accounts[1], { from: accounts[1] }, + ) + ) { await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[1], autoGas: 1.1, }); + businessCenter, 'join', { from: accounts[1], autoGas: 1.1 }, + ); } if (!await executor.executeContractCall( - businessCenter, 'isMember', accounts[2], { from: accounts[2], })) { + businessCenter, 'isMember', accounts[2], { from: accounts[2] }, + ) + ) { await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[2], autoGas: 1.1, }); + businessCenter, 'join', { from: accounts[2], autoGas: 1.1 }, + ); } }); @@ -288,27 +294,27 @@ describe('ServiceContract', function() { expect(contract).to.be.ok; }); - it('can store a service', async() => { + it('can store a service', async () => { const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); const service = await sc0.getService(contract, accounts[0]); expect(service).to.deep.eq(sampleService1); }); - it('can update a service', async() => { + it('can update a service', async () => { const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); await sc0.setService(contract, accounts[0], sampleService2, businessCenterDomain); const service = await sc0.getService(contract, accounts[0]); expect(service).to.deep.eq(sampleService2); }); - it('can send a service message', async() => { + it('can send a service message', async () => { const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); const callId = await sc0.sendCall(contract, accounts[0], sampleCall); const call = await sc0.getCall(contract, accounts[0], callId); expect(call.data).to.deep.eq(sampleCall); }); - it('cannot send a service message, that doesn\'t match the definition', async() => { + it('cannot send a service message, that doesn\'t match the definition', async () => { const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); const brokenCall = JSON.parse(JSON.stringify(sampleCall)); brokenCall.payload.someBogus = 123; @@ -316,46 +322,53 @@ describe('ServiceContract', function() { await expect(sendCallPromise).to.be.rejected; }); - it('can send an answer to a service message', async() => { + it('can send an answer to a service message', async () => { const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); await sc0.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[2]); + businessCenterDomain, contract.options.address, accounts[0], accounts[2], + ); const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', 0); await sharing.addSharing( - contract.options.address, accounts[0], accounts[2], '*', 0, contentKey); + contract.options.address, accounts[0], accounts[2], '*', 0, contentKey, + ); const callId = await sc0.sendCall( - contract, accounts[0], sampleCall, [accounts[2]]); + contract, accounts[0], sampleCall, [accounts[2]], + ); const call = await sc2.getCall(contract, accounts[0], callId); const answerId = await sc2.sendAnswer( - contract, accounts[2], sampleAnswer, callId, call.data.metadata.author); + contract, accounts[2], sampleAnswer, callId, call.data.metadata.author, + ); const answer = await sc2.getAnswer(contract, accounts[2], callId, answerId); expect(answer.data).to.deep.eq(sampleAnswer); }); - it('cannot send an answer to a service message, that doesn\'t match the definition', async() => { + it('cannot send an answer to a service message, that doesn\'t match the definition', async () => { const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); await sc0.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[2]); + businessCenterDomain, contract.options.address, accounts[0], accounts[2], + ); const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', 0); await sharing.addSharing( - contract.options.address, accounts[0], accounts[2], '*', 0, contentKey); + contract.options.address, accounts[0], accounts[2], '*', 0, contentKey, + ); const callId = await sc0.sendCall(contract, accounts[0], sampleCall, [accounts[2]]); const call = await sc2.getCall(contract, accounts[0], callId); const brokenAnswer = JSON.parse(JSON.stringify(sampleAnswer)); brokenAnswer.payload.someBogus = 123; const sendAnswerPromise = sc2.sendAnswer( - contract, accounts[2], brokenAnswer, callId, call.data.metadata.author); + contract, accounts[2], brokenAnswer, callId, call.data.metadata.author, + ); await expect(sendAnswerPromise).to.be.rejected; }); - it('can hold multiple calls', async() => { + it('can hold multiple calls', async () => { const sampleCalls = [Math.random(), Math.random(), Math.random()].map((rand) => { const currentSample = JSON.parse(JSON.stringify(sampleCall)); currentSample.payload.note += rand; return currentSample; }); const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); - for (let currentSample of sampleCalls) { + for (const currentSample of sampleCalls) { await sc0.sendCall(contract, accounts[0], currentSample); } expect((await sc0.getCall(contract, accounts[0], 0)).data).to.deep.eq(sampleCalls[0]); @@ -370,7 +383,7 @@ describe('ServiceContract', function() { return currentSample; }); const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); - for (let currentSample of sampleCalls) { + for (const currentSample of sampleCalls) { await sc0.sendCall(contract, accounts[0], currentSample); } await sc0.sendCall(contract, accounts[1], sampleCalls[0]); @@ -381,72 +394,84 @@ describe('ServiceContract', function() { }); it('does not allow calls to be read by every contract member without extending the sharing', - async() => { - const blockNr = await web3.eth.getBlockNumber(); - const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); - await sc0.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[2]); - const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', blockNr); - await sharing.addSharing( - contract.options.address, accounts[0], accounts[2], '*', blockNr, contentKey); - const callId = await sc0.sendCall(contract, accounts[0], sampleCall); - const call = await sc2.getCall(contract, accounts[2], callId); - expect(call.data).to.be.undefined; - }); + async () => { + const blockNr = await web3.eth.getBlockNumber(); + const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); + await sc0.inviteToContract( + businessCenterDomain, contract.options.address, accounts[0], accounts[2], + ); + const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', blockNr); + await sharing.addSharing( + contract.options.address, accounts[0], accounts[2], '*', blockNr, contentKey, + ); + const callId = await sc0.sendCall(contract, accounts[0], sampleCall); + const call = await sc2.getCall(contract, accounts[2], callId); + expect(call.data).to.be.undefined; + }); - it('allows calls to be read, when added to a calls sharing', async() => { + it('allows calls to be read, when added to a calls sharing', async () => { const blockNr = await web3.eth.getBlockNumber(); const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); await sc0.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[2]); + businessCenterDomain, contract.options.address, accounts[0], accounts[2], + ); const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', blockNr); await sharing.addSharing( - contract.options.address, accounts[0], accounts[2], '*', blockNr, contentKey); + contract.options.address, accounts[0], accounts[2], '*', blockNr, contentKey, + ); const callId = await sc0.sendCall(contract, accounts[0], sampleCall); await sc0.addToCallSharing(contract, accounts[0], callId, [accounts[2]]); const call = await sc2.getCall(contract, accounts[2], 0); expect(call.data).to.deep.eq(sampleCall); }); - it('does not allow answers to be read by other members than the original caller', async() => { + it('does not allow answers to be read by other members than the original caller', async () => { const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', 0); await sc0.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[1]); + businessCenterDomain, contract.options.address, accounts[0], accounts[1], + ); await sharing.addSharing( - contract.options.address, accounts[0], accounts[1], '*', 0, contentKey); + contract.options.address, accounts[0], accounts[1], '*', 0, contentKey, + ); await sc0.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[2]); + businessCenterDomain, contract.options.address, accounts[0], accounts[2], + ); await sharing.addSharing( - contract.options.address, accounts[0], accounts[2], '*', 0, contentKey); + contract.options.address, accounts[0], accounts[2], '*', 0, contentKey, + ); await sc0.sendCall(contract, accounts[0], sampleCall, [accounts[2]]); const call = await sc2.getCall(contract, accounts[0], 0); await sc2.sendAnswer(contract, accounts[2], sampleAnswer, 0, call.data.metadata.author); // create second service contract helper with fewer keys const limitedKeyProvider = TestUtils.getKeyProvider([ - nameResolver.soliditySha3.apply(nameResolver, - [nameResolver.soliditySha3(accounts[0]), nameResolver.soliditySha3(accounts[1])].sort()), + nameResolver.soliditySha3( + ...[ + nameResolver.soliditySha3(accounts[0]), + nameResolver.soliditySha3(accounts[1]), + ].sort(), + ), ]); const limitedSc = await TestUtils.getServiceContract(web3, ipfs, limitedKeyProvider); const answer = await limitedSc.getAnswer(contract, accounts[1], 0, 0); await expect(answer.data).to.be.undefined; }); - it('can retrieve the count for calls', async() => { + it('can retrieve the count for calls', async () => { const sampleCalls = [Math.random(), Math.random(), Math.random()].map((rand) => { const currentSample = JSON.parse(JSON.stringify(sampleCall)); currentSample.payload.note += rand; return currentSample; }); const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); - for (let currentSample of sampleCalls) { + for (const currentSample of sampleCalls) { await sc0.sendCall(contract, accounts[0], currentSample); } expect(await sc0.getCallCount(contract)).to.eq(sampleCalls.length); }); - it('can retrieve the count for answers', async() => { + it('can retrieve the count for answers', async () => { const sampleAnswers = [Math.random(), Math.random(), Math.random()].map((rand) => { const currentSample = JSON.parse(JSON.stringify(sampleAnswer)); currentSample.payload.note += rand; @@ -454,20 +479,22 @@ describe('ServiceContract', function() { }); const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); await sc0.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[2]); + businessCenterDomain, contract.options.address, accounts[0], accounts[2], + ); const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', 0); await sharing.addSharing( - contract.options.address, accounts[0], accounts[2], '*', 0, contentKey); + contract.options.address, accounts[0], accounts[2], '*', 0, contentKey, + ); await sc0.sendCall(contract, accounts[0], sampleCall, [accounts[2]]); const call = await sc2.getCall(contract, accounts[2], 0); - for (let currentSample of sampleAnswers) { + for (const currentSample of sampleAnswers) { await sc2.sendAnswer(contract, accounts[2], currentSample, 0, call.data.metadata.author); } const answerCount = await sc0.getAnswerCount(contract, 0); expect(answerCount).to.eq(sampleAnswers.length); }); - it('can retrieve all answers', async() => { + it('can retrieve all answers', async () => { const sampleAnswers = [Math.random(), Math.random(), Math.random()].map((rand) => { const currentSample = JSON.parse(JSON.stringify(sampleAnswer)); currentSample.payload.note += rand; @@ -475,13 +502,15 @@ describe('ServiceContract', function() { }); const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); await sc0.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[2]); + businessCenterDomain, contract.options.address, accounts[0], accounts[2], + ); const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', 0); await sharing.addSharing( - contract.options.address, accounts[0], accounts[2], '*', 0, contentKey); + contract.options.address, accounts[0], accounts[2], '*', 0, contentKey, + ); await sc0.sendCall(contract, accounts[0], sampleCall, [accounts[2]]); const call = await sc2.getCall(contract, accounts[0], 0); - for (let currentSample of sampleAnswers) { + for (const currentSample of sampleAnswers) { await sc2.sendAnswer(contract, accounts[2], currentSample, 0, call.data.metadata.author); } const answers = await sc0.getAnswers(contract, accounts[0], 0); @@ -491,44 +520,50 @@ describe('ServiceContract', function() { }); }); - it('can create answers and read and answer them with another user', async() => { + it('can create answers and read and answer them with another user', async () => { const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); await sc0.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[2]); + businessCenterDomain, contract.options.address, accounts[0], accounts[2], + ); const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', 0); await sharing.addSharing( - contract.options.address, accounts[0], accounts[2], '*', 0, contentKey); + contract.options.address, accounts[0], accounts[2], '*', 0, contentKey, + ); const callId = await sc0.sendCall(contract, accounts[0], sampleCall, [accounts[2]]); // retrieve call with other account, create answer const call = await sc2.getCall(contract, accounts[2], callId); expect(call.data).to.deep.eq(sampleCall); const answerId = await sc2.sendAnswer( - contract, accounts[2], sampleAnswer, callId, call.data.metadata.author); + contract, accounts[2], sampleAnswer, callId, call.data.metadata.author, + ); // retrieve answer with first account const answer = await sc2.getAnswer(contract, accounts[0], callId, answerId); expect(answer.data).to.deep.eq(sampleAnswer); }); - it('can create answers and gets only basic answer information if unable to decrypt', async() => { + it('can create answers and gets only basic answer information if unable to decrypt', async () => { const contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); await sc0.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[2]); + businessCenterDomain, contract.options.address, accounts[0], accounts[2], + ); const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', 0); await sharing.addSharing( - contract.options.address, accounts[0], accounts[2], '*', 0, contentKey); + contract.options.address, accounts[0], accounts[2], '*', 0, contentKey, + ); const callId = await sc0.sendCall(contract, accounts[0], sampleCall, [accounts[2]]); // retrieve call with other account, create answer const call = await sc2.getCall(contract, accounts[2], callId); expect(call.data).to.deep.eq(sampleCall); const answerId = await sc2.sendAnswer( - contract, accounts[2], sampleAnswer, callId, call.data.metadata.author); + contract, accounts[2], sampleAnswer, callId, call.data.metadata.author, + ); // retrieve answer with first account const answer0 = await sc0.getAnswer(contract, accounts[0], callId, answerId); - expect(answer0.data).to.deep.eq(sampleAnswer) + expect(answer0.data).to.deep.eq(sampleAnswer); const answers0 = await sc0.getAnswers(contract, accounts[2], callId); expect(answers0[answerId].data).to.deep.eq(sampleAnswer); @@ -548,16 +583,16 @@ describe('ServiceContract', function() { const callCount = 23; before(async () => { - sampleCalls = [...Array(callCount)].map(() => Math.random()).map((rand, i) => {; + sampleCalls = [...Array(callCount)].map(() => Math.random()).map((rand, i) => { const currentSample = JSON.parse(JSON.stringify(sampleCall)); currentSample.payload.note += i; return currentSample; }); sampleAnswers = []; - for (let i = 0; i < anwersCount; i++) { - const answer = JSON.parse(JSON.stringify(sampleAnswer)); - answer.payload.note += i; - sampleAnswers.push(answer); + for (let i = 0; i < anwersCount; i += 1) { + const answer = JSON.parse(JSON.stringify(sampleAnswer)); + answer.payload.note += i; + sampleAnswers.push(answer); } // if using existing contract @@ -567,25 +602,22 @@ describe('ServiceContract', function() { // if creating new contract contract = await sc0.create(accounts[0], businessCenterDomain, sampleService1); await sc0.inviteToContract( - businessCenterDomain, contract.options.address, accounts[0], accounts[2]); + businessCenterDomain, contract.options.address, accounts[0], accounts[2], + ); const contentKey = await sharing.getKey(contract.options.address, accounts[0], '*', 0); await sharing.addSharing( - contract.options.address, accounts[0], accounts[2], '*', 0, contentKey); - let callIndex = 0; - for (let currentSample of sampleCalls) { - console.log(`send test call ${callIndex++}`); + contract.options.address, accounts[0], accounts[2], '*', 0, contentKey, + ); + for (const currentSample of sampleCalls) { await sc0.sendCall(contract, accounts[0], currentSample, [accounts[2]]); } - let answerIndex = 0; - for (let answer of sampleAnswers) { - console.log(`send test answer ${answerIndex++}`); - await sc2.sendAnswer(contract, accounts[2], answer, anweredCallId, accounts[0]) + for (const answer of sampleAnswers) { + await sc2.sendAnswer(contract, accounts[2], answer, anweredCallId, accounts[0]); } - console.log(contract.options.address); }); describe('when retrieving calls', () => { - it('can retrieve calls', async() => { + it('can retrieve calls', async () => { const calls = await sc0.getCalls(contract, accounts[0]); expect(Object.keys(calls).length).to.eq(Math.min(sampleCalls.length, 10)); Object.keys(calls).forEach((callId, i) => { @@ -593,7 +625,7 @@ describe('ServiceContract', function() { }); }); - it('can retrieve calls with a limited page size', async() => { + it('can retrieve calls with a limited page size', async () => { const count = 2; const calls = await sc0.getCalls(contract, accounts[0], count); expect(Object.keys(calls).length).to.eq(Math.min(sampleCalls.length, count)); @@ -602,7 +634,7 @@ describe('ServiceContract', function() { }); }); - it('can retrieve calls with offset that results in a in a full page', async() => { + it('can retrieve calls with offset that results in a in a full page', async () => { const offset = 7; const calls = await sc0.getCalls(contract, accounts[0], 10, offset); expect(Object.keys(calls).length).to.eq(Math.min(sampleCalls.length - offset, 10)); @@ -611,7 +643,7 @@ describe('ServiceContract', function() { }); }); - it('can retrieve calls with offset that doesn\'t result not full page', async() => { + it('can retrieve calls with offset that doesn\'t result not full page', async () => { const offset = 17; const calls = await sc0.getCalls(contract, accounts[0], 10, offset); expect(Object.keys(calls).length).to.eq(Math.min(sampleCalls.length - offset, 10)); @@ -620,7 +652,7 @@ describe('ServiceContract', function() { }); }); - it('can retrieve calls with limited page size and offset', async() => { + it('can retrieve calls with limited page size and offset', async () => { const count = 2; const offset = 17; const calls = await sc0.getCalls(contract, accounts[0], 2, offset); @@ -630,7 +662,7 @@ describe('ServiceContract', function() { }); }); - it('can retrieve calls in reverse order', async() => { + it('can retrieve calls in reverse order', async () => { const calls = await sc0.getCalls(contract, accounts[0], 10, 0, true); expect(Object.keys(calls).length).to.eq(10); Object.keys(calls).reverse().forEach((callId, i) => { @@ -638,7 +670,7 @@ describe('ServiceContract', function() { }); }); - it('can retrieve calls in reverse order with a limited page size', async() => { + it('can retrieve calls in reverse order with a limited page size', async () => { const count = 2; const calls = await sc0.getCalls(contract, accounts[0], count, 0, true); expect(Object.keys(calls).length).to.eq(Math.min(sampleCalls.length, count)); @@ -647,7 +679,7 @@ describe('ServiceContract', function() { }); }); - it('can retrieve calls in reverse order with offset that results in a full page', async() => { + it('can retrieve calls in reverse order with offset that results in a full page', async () => { const offset = 7; const calls = await sc0.getCalls(contract, accounts[0], 10, offset, true); expect(Object.keys(calls).length).to.eq(Math.min(sampleCalls.length - offset, 10)); @@ -657,16 +689,16 @@ describe('ServiceContract', function() { }); it('can retrieve calls in reverse order with offset that doesn\'t result not full page', - async() => { - const offset = 17; - const calls = await sc0.getCalls(contract, accounts[0], 10, offset, true); - expect(Object.keys(calls).length).to.eq(Math.min(sampleCalls.length - offset, 10)); - Object.keys(calls).reverse().forEach((callId, i) => { - expect(calls[callId].data).to.deep.eq(sampleCalls[sampleCalls.length - 1 - i - offset]); + async () => { + const offset = 17; + const calls = await sc0.getCalls(contract, accounts[0], 10, offset, true); + expect(Object.keys(calls).length).to.eq(Math.min(sampleCalls.length - offset, 10)); + Object.keys(calls).reverse().forEach((callId, i) => { + expect(calls[callId].data).to.deep.eq(sampleCalls[sampleCalls.length - 1 - i - offset]); + }); }); - }); - it('can retrieve calls in reverse order with limited page size and offset', async() => { + it('can retrieve calls in reverse order with limited page size and offset', async () => { const count = 2; const offset = 17; const calls = await sc0.getCalls(contract, accounts[0], 2, offset, true); @@ -678,7 +710,7 @@ describe('ServiceContract', function() { }); describe('when retrieving answers', () => { - it('can retrieve answers', async() => { + it('can retrieve answers', async () => { const answers = await sc0.getAnswers(contract, accounts[0], anweredCallId); expect(Object.keys(answers).length).to.eq(Math.min(sampleAnswers.length, 10)); Object.keys(answers).forEach((answerId, i) => { @@ -686,7 +718,7 @@ describe('ServiceContract', function() { }); }); - it('can retrieve answers with a limited page size', async() => { + it('can retrieve answers with a limited page size', async () => { const count = 2; const answers = await sc0.getAnswers(contract, accounts[0], anweredCallId, count); expect(Object.keys(answers).length).to.eq(Math.min(sampleAnswers.length, count)); @@ -695,7 +727,7 @@ describe('ServiceContract', function() { }); }); - it('can retrieve answers with offset that results in a in a full page', async() => { + it('can retrieve answers with offset that results in a in a full page', async () => { const offset = 7; const answers = await sc0.getAnswers(contract, accounts[0], anweredCallId, 10, offset); expect(Object.keys(answers).length).to.eq(Math.min(sampleAnswers.length - offset, 10)); @@ -704,7 +736,7 @@ describe('ServiceContract', function() { }); }); - it('can retrieve answers with offset that doesn\'t result not full page', async() => { + it('can retrieve answers with offset that doesn\'t result not full page', async () => { const offset = 17; const answers = await sc0.getAnswers(contract, accounts[0], anweredCallId, 10, offset); expect(Object.keys(answers).length).to.eq(Math.min(sampleAnswers.length - offset, 10)); @@ -713,7 +745,7 @@ describe('ServiceContract', function() { }); }); - it('can retrieve answers with limited page size and offset', async() => { + it('can retrieve answers with limited page size and offset', async () => { const count = 2; const offset = 17; const answers = await sc0.getAnswers(contract, accounts[0], anweredCallId, 2, offset); @@ -723,7 +755,7 @@ describe('ServiceContract', function() { }); }); - it('can retrieve answers in reverse order', async() => { + it('can retrieve answers in reverse order', async () => { const answers = await sc0.getAnswers(contract, accounts[0], anweredCallId, 10, 0, true); expect(Object.keys(answers).length).to.eq(10); Object.keys(answers).reverse().forEach((answerId, i) => { @@ -731,7 +763,7 @@ describe('ServiceContract', function() { }); }); - it('can retrieve answers in reverse order with a limited page size', async() => { + it('can retrieve answers in reverse order with a limited page size', async () => { const count = 2; const answers = await sc0.getAnswers(contract, accounts[0], anweredCallId, count, 0, true); expect(Object.keys(answers).length).to.eq(Math.min(sampleAnswers.length, count)); @@ -741,37 +773,42 @@ describe('ServiceContract', function() { }); it('can retrieve answers in reverse order with offset that results in a full page', - async() => { - const offset = 7; - const answers = await sc0.getAnswers( - contract, accounts[0], anweredCallId, 10, offset, true); - expect(Object.keys(answers).length).to.eq(Math.min(sampleAnswers.length - offset, 10)); - Object.keys(answers).reverse().forEach((answerId, i) => { - expect(answers[answerId].data).to.deep.eq( - sampleAnswers[sampleAnswers.length - 1 - i - offset]); + async () => { + const offset = 7; + const answers = await sc0.getAnswers( + contract, accounts[0], anweredCallId, 10, offset, true, + ); + expect(Object.keys(answers).length).to.eq(Math.min(sampleAnswers.length - offset, 10)); + Object.keys(answers).reverse().forEach((answerId, i) => { + expect(answers[answerId].data).to.deep.eq( + sampleAnswers[sampleAnswers.length - 1 - i - offset], + ); + }); }); - }); it('can retrieve answers in reverse with offset that doesn\'t result not full page', - async() => { - const offset = 17; - const answers = await sc0.getAnswers( - contract, accounts[0], anweredCallId, 10, offset, true); - expect(Object.keys(answers).length).to.eq(Math.min(sampleAnswers.length - offset, 10)); - Object.keys(answers).reverse().forEach((answerId, i) => { - expect(answers[answerId].data).to.deep.eq( - sampleAnswers[sampleAnswers.length - 1 - i - offset]); + async () => { + const offset = 17; + const answers = await sc0.getAnswers( + contract, accounts[0], anweredCallId, 10, offset, true, + ); + expect(Object.keys(answers).length).to.eq(Math.min(sampleAnswers.length - offset, 10)); + Object.keys(answers).reverse().forEach((answerId, i) => { + expect(answers[answerId].data).to.deep.eq( + sampleAnswers[sampleAnswers.length - 1 - i - offset], + ); + }); }); - }); - it('can retrieve answers in reverse with limited page size and offset', async() => { + it('can retrieve answers in reverse with limited page size and offset', async () => { const count = 2; const offset = 17; const answers = await sc0.getAnswers(contract, accounts[0], anweredCallId, 2, offset, true); expect(Object.keys(answers).length).to.eq(Math.min(sampleAnswers.length - offset, count)); Object.keys(answers).reverse().forEach((answerId, i) => { expect(answers[answerId].data).to.deep.eq( - sampleAnswers[sampleAnswers.length - 1 - i - offset]); + sampleAnswers[sampleAnswers.length - 1 - i - offset], + ); }); }); }); @@ -784,7 +821,8 @@ describe('ServiceContract', function() { // get cryptor for annotating encryption of properties const cryptor = sc0.options.cryptoProvider.getCryptorByCryptoAlgo( - sc0.options.defaultCryptoAlgo); + sc0.options.defaultCryptoAlgo, + ); const secretPayload = { someNumber: Math.random(), someText: `I like randomNumbers in payload, for example: ${Math.random()}`, @@ -793,12 +831,14 @@ describe('ServiceContract', function() { callForNesting.payload.privateData = { private: secretPayload, cryptoInfo: cryptor.getCryptoInfo( - sc0.options.nameResolver.soliditySha3(contract.options.address)), + sc0.options.nameResolver.soliditySha3(contract.options.address), + ), }; callForNesting.metadata.privateData = { private: secretMetadata, cryptoInfo: cryptor.getCryptoInfo( - sc0.options.nameResolver.soliditySha3(contract.options.address)), + sc0.options.nameResolver.soliditySha3(contract.options.address), + ), }; // send it as usual (to-encrypt properties are encrypted automatically); invite participant const callId = await sc0.sendCall(contract, accounts[0], callForNesting, [accounts[2]]); @@ -833,7 +873,8 @@ describe('ServiceContract', function() { // add sharing for participent await sc0.addToCallSharing( - contract, accounts[0], callId, [accounts[2]], null, null, 'privateData'); + contract, accounts[0], callId, [accounts[2]], null, null, 'privateData', + ); // fetch again sc2.options.sharing.clearCache(); call = (await sc2.getCall(contract, accounts[2], callId)).data; diff --git a/src/contracts/service-contract/service-contract.ts b/src/contracts/service-contract/service-contract.ts index 2db982b3..87bca711 100644 --- a/src/contracts/service-contract/service-contract.ts +++ b/src/contracts/service-contract/service-contract.ts @@ -17,23 +17,22 @@ the following URL: https://evan.network/license/ */ -import _ = require('lodash'); -import crypto = require('crypto'); -import prottle = require('prottle'); - import { - ContractLoader, DfsInterface, Envelope, KeyProviderInterface, - Logger, Validator, } from '@evan.network/dbcp'; -import { BaseContract, BaseContractOptions, } from '../base-contract/base-contract'; +import { BaseContract, BaseContractOptions } from '../base-contract/base-contract'; import { CryptoProvider } from '../../encryption/crypto-provider'; import { Sharing } from '../sharing'; +import _ = require('lodash'); +import crypto = require('crypto'); +import prottle = require('prottle'); + + const requestWindowSize = 10; const uintMax = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; @@ -41,9 +40,9 @@ const serviceSchema = { type: 'object', additionalProperties: false, properties: { - serviceName: { type: 'string', }, - requestParameters: { type: 'object', }, - responseParameters: { type: 'object', }, + serviceName: { type: 'string' }, + requestParameters: { type: 'object' }, + responseParameters: { type: 'object' }, }, }; @@ -76,12 +75,12 @@ export interface CallResult { * options for ServiceContract constructor */ export interface ServiceContractOptions extends BaseContractOptions { - cryptoProvider: CryptoProvider, - dfs: DfsInterface, - keyProvider: KeyProviderInterface, - sharing: Sharing, - web3: any, - defaultCryptoAlgo?: string, + cryptoProvider: CryptoProvider; + dfs: DfsInterface; + keyProvider: KeyProviderInterface; + sharing: Sharing; + web3: any; + defaultCryptoAlgo?: string; } /** @@ -91,13 +90,18 @@ export interface ServiceContractOptions extends BaseContractOptions { */ export class ServiceContract extends BaseContract { public options: ServiceContractOptions; + private readonly encodingUnencrypted = 'utf-8'; + private readonly encodingEncrypted = 'hex'; + private readonly encodingUnencryptedHash = 'hex'; + private readonly cryptoAlgorithHashes = 'aesEcb'; + private serviceDefinition; - constructor(optionsInput: ServiceContractOptions) { + public constructor(optionsInput: ServiceContractOptions) { super(optionsInput as BaseContractOptions); this.options = optionsInput; if (!this.options.defaultCryptoAlgo) { @@ -115,34 +119,41 @@ export class ServiceContract extends BaseContract { * @return {Promise} resolved when done */ public async addToCallSharing( - contract: any|string, - accountId: string, - callId: number, - to: string[], - hashKey?: string, - contentKey?: string, - section = '*'): Promise { - const serviceContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('ServiceContractInterface', contract); + contract: any|string, + accountId: string, + callId: number, + to: string[], + hashKey?: string, + contentKey?: string, + section = '*', + ): Promise { + const serviceContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('ServiceContractInterface', contract); const callIdHash = this.numberToBytes32(callId); const [blockNr, hashKeyToShare] = await Promise.all([ this.options.web3.eth.getBlockNumber(), hashKey || this.options.sharing.getHashKey( - serviceContract.options.address, accountId, callIdHash), + serviceContract.options.address, accountId, callIdHash, + ), ]); - const contentKeyToShare = contentKey || - (await this.options.sharing.getKey( - serviceContract.options.address, accountId, section, blockNr, callIdHash)); + const contentKeyToShare = contentKey + || (await this.options.sharing.getKey( + serviceContract.options.address, accountId, section, blockNr, callIdHash, + )); const sharings = await this.options.sharing.getSharingsFromContract( - serviceContract, callIdHash); - for (let target of to) { + serviceContract, callIdHash, + ); + for (const target of to) { await this.options.sharing.extendSharings( - sharings, accountId, target, section, 0, contentKeyToShare, null); + sharings, accountId, target, section, 0, contentKeyToShare, null, + ); await this.options.sharing.extendSharings( - sharings, accountId, target, '*', 'hashKey', hashKeyToShare, null); + sharings, accountId, target, '*', 'hashKey', hashKeyToShare, null, + ); } await this.options.sharing.saveSharingsToContract( - serviceContract.options.address, sharings, accountId, callIdHash); + serviceContract.options.address, sharings, accountId, callIdHash, + ); } /** @@ -156,13 +167,13 @@ export class ServiceContract extends BaseContract { * @return {Promise} contract instance */ public async create( - accountId: string, - businessCenterDomain: string, - service: any, - descriptionDfsHash = '0x0000000000000000000000000000000000000000000000000000000000000000', - ): Promise { + accountId: string, + businessCenterDomain: string, + service: any, + descriptionDfsHash = '0x0000000000000000000000000000000000000000000000000000000000000000', + ): Promise { // validate service definition - const validator = new Validator({ schema: serviceSchema, }); + const validator = new Validator({ schema: serviceSchema }); const checkFails = validator.validate(service); if (checkFails !== true) { throw new Error(`validation of service values failed with: ${JSON.stringify(checkFails)}`); @@ -170,21 +181,27 @@ export class ServiceContract extends BaseContract { const contractP = (async () => { const contractId = await super.createUninitialized( - 'service', accountId, businessCenterDomain, descriptionDfsHash); + 'service', accountId, businessCenterDomain, descriptionDfsHash, + ); return this.options.loader.loadContract('ServiceContractInterface', contractId); })(); // create sharing key for owner const cryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo( - this.options.defaultCryptoAlgo); + this.options.defaultCryptoAlgo, + ); const hashCryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo( - this.cryptoAlgorithHashes); + this.cryptoAlgorithHashes, + ); const [contentKey, hashKey, contract] = await Promise.all( - [cryptor.generateKey(), hashCryptor.generateKey(), contractP]); + [cryptor.generateKey(), hashCryptor.generateKey(), contractP], + ); await this.options.sharing.addSharing( - contract.options.address, accountId, accountId, '*', 0, contentKey); + contract.options.address, accountId, accountId, '*', 0, contentKey, + ); await this.options.sharing.ensureHashKey( - contract.options.address, accountId, accountId, hashKey); + contract.options.address, accountId, accountId, hashKey, + ); // add service after sharing has been added await this.setService(contract, accountId, service, businessCenterDomain, true); @@ -202,21 +219,27 @@ export class ServiceContract extends BaseContract { * @return {Promise} the answer */ public async getAnswer( - contract: any|string, accountId: string, callId: number, answerIndex: number): Promise { - const serviceContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('ServiceContractInterface', contract); + contract: any|string, + accountId: string, + callId: number, + answerIndex: number, + ): Promise { + const serviceContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('ServiceContractInterface', contract); const queryResult = await this.options.executor.executeContractCall( - serviceContract, 'getAnswers', callId, answerIndex); + serviceContract, 'getAnswers', callId, answerIndex, + ); const result: any = {}; - ['hash', 'owner', 'created', 'parent'].forEach((key) => { result[key] = queryResult[key][0]; }); + ['hash', 'owner', 'created', 'parent'].forEach((key) => { [result[key]] = queryResult[key]; }); const decryptedHash = await this.decryptHash( - queryResult.hash[0], serviceContract, accountId, this.numberToBytes32(callId)); + queryResult.hash[0], serviceContract, accountId, this.numberToBytes32(callId), + ); if (decryptedHash) { result.data = await this.decrypt( (await this.options.dfs.get(decryptedHash)).toString('utf-8'), serviceContract, accountId, - '*' + '*', ); } return result; @@ -230,10 +253,11 @@ export class ServiceContract extends BaseContract { * @return {Promise} number of answers */ public async getAnswerCount(contract: any|string, callId: number): Promise { - const serviceContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('ServiceContractInterface', contract); - const { answerCount} = await this.options.executor.executeContractCall( - serviceContract, 'calls', callId); + const serviceContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('ServiceContractInterface', contract); + const { answerCount } = await this.options.executor.executeContractCall( + serviceContract, 'calls', callId, + ); return parseInt(answerCount, 10); } @@ -249,18 +273,20 @@ export class ServiceContract extends BaseContract { * @return {Promise} the calls */ public async getAnswers( - contract: any|string, - accountId: string, - callId: number, - count = 10, - offset = 0, - reverse = false): Promise { - const serviceContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('ServiceContractInterface', contract); + contract: any|string, + accountId: string, + callId: number, + count = 10, + offset = 0, + reverse = false, + ): Promise { + const serviceContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('ServiceContractInterface', contract); // get entries const { entries, indices } = await this.getEntries( - serviceContract, 'answers', callId, count, offset, reverse); + serviceContract, 'answers', callId, count, offset, reverse, + ); // decrypt contents const result = {}; @@ -268,7 +294,8 @@ export class ServiceContract extends BaseContract { const callIdString = this.numberToBytes32(callId); const tasks = indices.map((index) => async () => { const decryptedHash = await this.decryptHash( - entries[index].hash, serviceContract, accountId, callIdString); + entries[index].hash, serviceContract, accountId, callIdString, + ); result[index] = entries[index]; if (decryptedHash) { result[index].data = await this.decrypt( @@ -297,13 +324,15 @@ export class ServiceContract extends BaseContract { * @return {Promise} the call */ public async getCall(contract: any|string, accountId: string, callId: number): Promise { - const serviceContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('ServiceContractInterface', contract); + const serviceContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('ServiceContractInterface', contract); const callIdString = this.numberToBytes32(callId); - let call = await this.options.executor.executeContractCall( - serviceContract, 'calls', callIdString); + const call = await this.options.executor.executeContractCall( + serviceContract, 'calls', callIdString, + ); const decryptedHash = await this.decryptHash( - call.hash, serviceContract, accountId, callIdString); + call.hash, serviceContract, accountId, callIdString, + ); if (decryptedHash) { call.data = await this.decrypt( (await this.options.dfs.get(decryptedHash)).toString('utf-8'), @@ -316,7 +345,7 @@ export class ServiceContract extends BaseContract { return call; } - /** + /** * get all calls from a contract * * @param {any|string} contract smart contract instance or contract ID @@ -327,13 +356,14 @@ export class ServiceContract extends BaseContract { * @return {Promise} the calls */ public async getCalls( - contract: any|string, - accountId: string, - count = 10, - offset = 0, - reverse = false): Promise { - const serviceContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('ServiceContractInterface', contract); + contract: any|string, + accountId: string, + count = 10, + offset = 0, + reverse = false, + ): Promise { + const serviceContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('ServiceContractInterface', contract); // get entries const { entries, indices } = await this.getEntries(serviceContract, @@ -378,8 +408,8 @@ export class ServiceContract extends BaseContract { * @return {Promise} number of calls */ public async getCallCount(contract: any|string): Promise { - const serviceContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('ServiceContractInterface', contract); + const serviceContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('ServiceContractInterface', contract); const count = await this.options.executor.executeContractCall(serviceContract, 'callCount'); return parseInt(count, 10); } @@ -392,10 +422,11 @@ export class ServiceContract extends BaseContract { * @return {Promise} account id of call owner */ public async getCallOwner(contract: any|string, callId: number): Promise { - const serviceContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('ServiceContractInterface', contract); + const serviceContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('ServiceContractInterface', contract); return (await this.options.executor.executeContractCall( - serviceContract, 'calls', callId)).owner; + serviceContract, 'calls', callId, + )).owner; } /** @@ -406,16 +437,17 @@ export class ServiceContract extends BaseContract { * @return {Promise} service description */ public async getService(contract: any|string, accountId: string): Promise { - const serviceContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('ServiceContractInterface', contract); + const serviceContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('ServiceContractInterface', contract); const encryptedHash = await this.options.executor.executeContractCall( - serviceContract, 'service'); + serviceContract, 'service', + ); const decryptedHash = await this.decryptHash(encryptedHash, serviceContract, accountId); const decrypted = await this.decrypt( (await this.options.dfs.get(decryptedHash)).toString('utf-8'), serviceContract, accountId, - '*' + '*', ); this.serviceDefinition = decrypted; return decrypted; @@ -432,27 +464,28 @@ export class ServiceContract extends BaseContract { * @return {Promise} resolved when done */ public async sendAnswer( - contract: any|string, - accountId: string, - answer: any, - callId: number, - callAuthor: string, - callParent = uintMax): Promise { - const serviceContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('ServiceContractInterface', contract); + contract: any|string, + accountId: string, + answer: any, + callId: number, + callAuthor: string, + callParent = uintMax, + ): Promise { + const serviceContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('ServiceContractInterface', contract); // validate call if (!this.serviceDefinition) { // this.serviceDefinition is set and updated via side effects in getService and setService await this.getService(serviceContract, accountId); } - const validator = new Validator({ schema: this.serviceDefinition.responseParameters, }); + const validator = new Validator({ schema: this.serviceDefinition.responseParameters }); const checkFails = validator.validate(answer); if (checkFails !== true) { throw new Error(`validation of input values failed with: ${JSON.stringify(checkFails)}`); } - const blockNr = 0; // will be ignored as callAuthor is set + const blockNr = 0; // will be ignored as callAuthor is set // subproperties metadata.fnord and payload.fnord use the same key, // so track keys for subproperties and cryptors like 'fnord' here @@ -462,23 +495,25 @@ export class ServiceContract extends BaseContract { // encrypt properties const genericKey = this.options.nameResolver.soliditySha3('*'); const contentKey = await this.options.sharing.getKey( - serviceContract.options.address, accountId, '*', blockNr, this.numberToBytes32(callId)); + serviceContract.options.address, accountId, '*', blockNr, this.numberToBytes32(callId), + ); const generateKeys = async (property) => { innerPropertiesToEncrpt[property] = []; if (answer[property]) { - for (let key of Object.keys(answer[property])) { - if (answer[property][key].hasOwnProperty('private') && - answer[property][key].hasOwnProperty('cryptoInfo') && - !innerPropertiesToEncrpt[property][key]) { + for (const key of Object.keys(answer[property])) { + if (Object.prototype.hasOwnProperty.call(answer[property][key], 'private') + && Object.prototype.hasOwnProperty.call(answer[property][key], 'cryptoInfo') + && !innerPropertiesToEncrpt[property][key]) { innerPropertiesToEncrpt[property].push(key); innerEncryptionData[key] = {}; innerEncryptionData[key].cryptor = this.options.cryptoProvider.getCryptorByCryptoInfo( - answer[property][key].cryptoInfo); + answer[property][key].cryptoInfo, + ); // if we have properties to be encrypted with the original // content key (e.g. files/binaries) - if (answer[property][key].cryptoInfo.originator && - answer[property][key].cryptoInfo.originator === genericKey) { + if (answer[property][key].cryptoInfo.originator + && answer[property][key].cryptoInfo.originator === genericKey) { // use the previous generated content key innerEncryptionData[key].key = contentKey; } @@ -487,9 +522,10 @@ export class ServiceContract extends BaseContract { } }; - // run once for metadata and once for payload, await them sequentially to track already generated keys - const answerKeys = Object.keys(answer) - for (let key of answerKeys) { + // run once for metadata and once for payload, + // await them sequentially to track already generated keys + const answerKeys = Object.keys(answer); + for (const key of answerKeys) { await generateKeys(key); } @@ -508,7 +544,8 @@ export class ServiceContract extends BaseContract { const stateMd5 = crypto.createHash('md5').update(encrypted).digest('hex'); const answerHash = await this.options.dfs.add(stateMd5, Buffer.from(encrypted)); const hashKey = await this.options.sharing.getHashKey( - serviceContract.options.address, accountId, this.numberToBytes32(callId)); + serviceContract.options.address, accountId, this.numberToBytes32(callId), + ); const encryptdHash = await this.encryptHash(answerHash, serviceContract, accountId, hashKey); const answerId = await this.options.executor.executeContractTransaction( serviceContract, @@ -526,7 +563,7 @@ export class ServiceContract extends BaseContract { callParent, ); return parseInt(answerId, 16); - }; + } /** * send a call to a service @@ -537,19 +574,20 @@ export class ServiceContract extends BaseContract { * @return {Promise} returns id of new call */ public async sendCall( - contract: any|string, - accountId: string, - call: any, - to: string[] = []): Promise { - const serviceContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('ServiceContractInterface', contract); + contract: any|string, + accountId: string, + call: any, + to: string[] = [], + ): Promise { + const serviceContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('ServiceContractInterface', contract); // validate call if (!this.serviceDefinition) { // this.serviceDefinition is set and updated via side effects in getService and setService await this.getService(serviceContract, accountId); } - const validator = new Validator({ schema: this.serviceDefinition.requestParameters, }); + const validator = new Validator({ schema: this.serviceDefinition.requestParameters }); const checkFails = validator.validate(call); if (checkFails !== true) { throw new Error(`validation of input values failed with: ${JSON.stringify(checkFails)}`); @@ -565,9 +603,11 @@ export class ServiceContract extends BaseContract { // create keys for new call (outer properties) const cryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo( - this.options.defaultCryptoAlgo); + this.options.defaultCryptoAlgo, + ); const hashCryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo( - this.cryptoAlgorithHashes); + this.cryptoAlgorithHashes, + ); const [contentKey, hashKey, blockNr] = await Promise.all([ cryptor.generateKey(), hashCryptor.generateKey(), @@ -579,19 +619,20 @@ export class ServiceContract extends BaseContract { const generateKeys = async (property) => { innerPropertiesToEncrpt[property] = []; if (callCopy[property]) { - for (let key of Object.keys(callCopy[property])) { - if (callCopy[property][key].hasOwnProperty('private') && - callCopy[property][key].hasOwnProperty('cryptoInfo') && - !innerPropertiesToEncrpt[property][key]) { + for (const key of Object.keys(callCopy[property])) { + if (Object.prototype.hasOwnProperty.call(callCopy[property][key], 'private') + && Object.prototype.hasOwnProperty.call(callCopy[property][key], 'cryptoInfo') + && !innerPropertiesToEncrpt[property][key]) { innerPropertiesToEncrpt[property].push(key); innerEncryptionData[key] = {}; innerEncryptionData[key].cryptor = this.options.cryptoProvider.getCryptorByCryptoInfo( - callCopy[property][key].cryptoInfo); + callCopy[property][key].cryptoInfo, + ); // if we have properties to be encrypted with the // original content key (e.g. files/binaries) - if (callCopy[property][key].cryptoInfo.originator && - callCopy[property][key].cryptoInfo.originator === genericKey) { + if (callCopy[property][key].cryptoInfo.originator + && callCopy[property][key].cryptoInfo.originator === genericKey) { // use the previous generated content key innerEncryptionData[key].key = contentKey; } else { @@ -603,8 +644,8 @@ export class ServiceContract extends BaseContract { }; // run once for metadata and once for payload, // await them sequentially to track already generated keys - const callKeys = Object.keys(callCopy) - for (let key of callKeys) { + const callKeys = Object.keys(callCopy); + for (const key of callKeys) { await generateKeys(key); } @@ -645,17 +686,20 @@ export class ServiceContract extends BaseContract { // for each to, add sharing keys; owner is added to add his/her keys as well await this.addToCallSharing( - serviceContract, accountId, callIdUint256, to.concat(accountId), hashKey, contentKey); + serviceContract, accountId, callIdUint256, to.concat(accountId), hashKey, contentKey, + ); // if subproperties were encryted, keep them for owner as well const innerEncryptionKeys = Object.keys(innerEncryptionData); if (innerEncryptionKeys.length) { const sharings = await this.options.sharing.getSharingsFromContract(serviceContract, callId); - for (let propertyName of innerEncryptionKeys) { + for (const propertyName of innerEncryptionKeys) { await this.options.sharing.extendSharings( - sharings, accountId, accountId, propertyName, 0, innerEncryptionData[propertyName].key); + sharings, accountId, accountId, propertyName, 0, innerEncryptionData[propertyName].key, + ); } await this.options.sharing.saveSharingsToContract( - serviceContract.options.address, sharings, accountId, callId); + serviceContract.options.address, sharings, accountId, callId, + ); } // return id of new call @@ -675,20 +719,21 @@ export class ServiceContract extends BaseContract { * @return {Promise} resolved when done */ public async setService( - contract: any|string, - accountId: string, - service: any, - businessCenterDomain: string, - skipValidation?): Promise { - const serviceContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('ServiceContractInterface', contract); + contract: any|string, + accountId: string, + service: any, + businessCenterDomain: string, + skipValidation?: boolean, + ): Promise { + const serviceContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('ServiceContractInterface', contract); if (!skipValidation) { // validate service definition - const validator = new Validator({ schema: serviceSchema, }); + const validator = new Validator({ schema: serviceSchema }); const checkFails = validator.validate(service); if (checkFails !== true) { - throw new Error('validation of service definition failed with: ' + - JSON.stringify(checkFails)); + throw new Error(`validation of service definition failed with: ${ + JSON.stringify(checkFails)}`); } } const blockNr = await this.options.web3.eth.getBlockNumber(); @@ -696,7 +741,7 @@ export class ServiceContract extends BaseContract { const encrypted = await this.encrypt(service, serviceContract, accountId, '*', blockNr); const stateMd5 = crypto.createHash('md5').update(encrypted).digest('hex'); const serviceHash = await this.options.dfs.add(stateMd5, Buffer.from(encrypted)); - return await this.encryptHash(serviceHash, serviceContract, accountId); + return this.encryptHash(serviceHash, serviceContract, accountId); })(); const [businessCenterAddress, encryptdHash] = await Promise.all([ this.options.nameResolver.getAddress(businessCenterDomain), @@ -704,7 +749,7 @@ export class ServiceContract extends BaseContract { ]); await this.options.executor.executeContractTransaction( serviceContract, - 'setService', {from: accountId, autoGas: 1.1, }, + 'setService', { from: accountId, autoGas: 1.1 }, businessCenterAddress, encryptdHash, ); @@ -722,11 +767,12 @@ export class ServiceContract extends BaseContract { * @return {Promise} decrypted message or null (if unable to decyrypt) */ private async decrypt( - toDecrypt: string, - contract: any, - accountId: string, - propertyName: string, - callId?: string): Promise { + toDecrypt: string, + contract: any, + accountId: string, + propertyName: string, + callId?: string, + ): Promise { try { const envelope: Envelope = JSON.parse(toDecrypt); const cryptor = this.options.cryptoProvider.getCryptorByCryptoInfo(envelope.cryptoInfo); @@ -735,32 +781,36 @@ export class ServiceContract extends BaseContract { if (!contentKey) { // check if encrypted via sharing contentKey = await this.options.sharing.getKey( - contract.options.address, accountId, propertyName, envelope.cryptoInfo.block, callId); + contract.options.address, accountId, propertyName, envelope.cryptoInfo.block, callId, + ); } if (!contentKey) { - throw new Error(`could not decrypt data, no content key found for contract ` + - `"${contract.options.address}" and account "${accountId}"`); + throw new Error('could not decrypt data, no content key found for contract ' + + `"${contract.options.address}" and account "${accountId}"`); } const decryptedObject = await cryptor.decrypt( - Buffer.from(envelope.private, this.encodingEncrypted), { key: contentKey, }); + Buffer.from(envelope.private, this.encodingEncrypted), { key: contentKey }, + ); await Promise.all(Object.keys(decryptedObject).map(async (property) => { await Promise.all(Object.keys(decryptedObject[property]).map(async (key) => { - if (decryptedObject[property][key].hasOwnProperty('private') && - decryptedObject[property][key].hasOwnProperty('cryptoInfo')) { + if (Object.prototype.hasOwnProperty.call(decryptedObject[property][key], 'private') + && Object.prototype.hasOwnProperty.call(decryptedObject[property][key], 'cryptoInfo')) { try { const innerCryptor = this.options.cryptoProvider.getCryptorByCryptoInfo( - decryptedObject[property][key].cryptoInfo); + decryptedObject[property][key].cryptoInfo, + ); const envelopeInner = decryptedObject[property][key]; const contentKeyInner = await this.options.sharing.getKey( - contract.options.address, accountId, key, envelopeInner.cryptoInfo.block, callId); + contract.options.address, accountId, key, envelopeInner.cryptoInfo.block, callId, + ); decryptedObject[property][key] = await innerCryptor.decrypt( Buffer.from(envelopeInner.private, this.encodingEncrypted), { key: contentKeyInner }, ); } catch (ex) { - this.log(`could not decrypt inner service message part ` + - `${property}/${key}; ${ex.message || ex}`, 'info') + this.log('could not decrypt inner service message part ' + + `${property}/${key}; ${ex.message || ex}`, 'info'); } } })); @@ -768,9 +818,10 @@ export class ServiceContract extends BaseContract { return decryptedObject; } catch (ex) { - this.log(`could not decrypt service contract message "${toDecrypt}" for contract ` + - `"${contract.options.address}" with accountId "${accountId}" in section "${propertyName}"` + - callId ? (' for call ' + callId) : '', + this.log( + `${`could not decrypt service contract message "${toDecrypt}" for contract ` + + `"${contract.options.address}" with accountId "${accountId}" in section "${propertyName}"`}${ + callId}` ? (` for call ${callId}`) : '', 'debug', ); return null; @@ -787,27 +838,32 @@ export class ServiceContract extends BaseContract { * @return {Promise} decrypted envelope or null (if unable to decyrypt) */ private async decryptHash( - toDecrypt: string, contract: any, accountId: string, callId?: string): Promise { - const dataContract = (typeof contract === 'object') ? - contract : this.options.loader.loadContract('ServiceContractInterface', contract); + toDecrypt: string, + contract: any, + accountId: string, + callId?: string, + ): Promise { + const dataContract = (typeof contract === 'object') + ? contract : this.options.loader.loadContract('ServiceContractInterface', contract); try { // decode hash const cryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo(this.cryptoAlgorithHashes); const hashKey = await this.options.sharing.getHashKey( - dataContract.options.address, accountId, callId); + dataContract.options.address, accountId, callId, + ); if (!hashKey) { - throw new Error(`no hashKey key found for contract "${dataContract.options.address}" ` + - `and account "${accountId}"`); + throw new Error(`no hashKey key found for contract "${dataContract.options.address}" ` + + `and account "${accountId}"`); } const decryptedBuffer = await cryptor.decrypt( - Buffer.from(toDecrypt.substr(2), this.encodingEncrypted), { key: hashKey, }); + Buffer.from(toDecrypt.substr(2), this.encodingEncrypted), { key: hashKey }, + ); return `0x${decryptedBuffer.toString(this.encodingUnencryptedHash)}`; } catch (ex) { - this.log(`could not decrypt service contract hash "${toDecrypt}" for contract ` + - `"${dataContract.options.address}" with account id "${accountId}"` + - callId ? (' for call ' + callId) : '', - 'debug', - ); + this.log(`${`could not decrypt service contract hash "${toDecrypt}" for contract ` + + `"${dataContract.options.address}" with account id "${accountId}"`}${ + callId}` ? (` for call ${callId}`) : '', + 'debug'); return null; } } @@ -828,52 +884,57 @@ export class ServiceContract extends BaseContract { * @return {Promise} stringified {Envelope} */ private async encrypt( - toEncrypt: any, - contract: any, - from: string, - propertyName: string, - block: number, - to?: string, - key?: Buffer, - innerPropertiesToEncrpt?: any, - innerEncryptionData?: any): Promise { + toEncrypt: any, + contract: any, + from: string, + propertyName: string, + block: number, + to?: string, + key?: Buffer, + innerPropertiesToEncrpt?: any, + innerEncryptionData?: any, + ): Promise { + const toEncryptParameter = toEncrypt; // helper for encrypting properties const encryptSubProperties = async (property) => { if (innerPropertiesToEncrpt[property]) { await Promise.all(innerPropertiesToEncrpt[property].map(async (keyInner) => { - if (innerPropertiesToEncrpt[property].includes(keyInner) && - toEncrypt[property][keyInner]) { + if (innerPropertiesToEncrpt[property].includes(keyInner) + && toEncryptParameter[property][keyInner]) { // encrypt with content key const encryptedBufferInner = await innerEncryptionData[keyInner].cryptor.encrypt( - toEncrypt[property][keyInner].private, { key: innerEncryptionData[keyInner].key, }); + toEncryptParameter[property][keyInner].private, + { key: innerEncryptionData[keyInner].key }, + ); const encryptedProperty = encryptedBufferInner.toString(this.encodingEncrypted); const envelopeInner: Envelope = { private: encryptedProperty, - cryptoInfo: toEncrypt[property][keyInner].cryptoInfo, + cryptoInfo: toEncryptParameter[property][keyInner].cryptoInfo, }; envelopeInner.cryptoInfo.block = block; - toEncrypt[property][keyInner] = envelopeInner; + toEncryptParameter[property][keyInner] = envelopeInner; } })); } }; // encrypt properties if (innerPropertiesToEncrpt) { - await Promise.all(Object.keys(toEncrypt).map( - async (toEncryptKey) => encryptSubProperties(toEncryptKey))); + await Promise.all(Object.keys(toEncryptParameter).map( + async (toEncryptKey) => encryptSubProperties(toEncryptKey), + )); } // get content key from contract const cryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo( - this.options.defaultCryptoAlgo); + this.options.defaultCryptoAlgo, + ); let cryptoInfo; let contentKey; if (to) { // directed message, encrypted with comm key const fromHash = this.options.nameResolver.soliditySha3(from); const toHash = this.options.nameResolver.soliditySha3(to); - const combinedHash = this.options.nameResolver.soliditySha3.apply( - this.options.nameResolver, [fromHash, toHash].sort()); + const combinedHash = this.options.nameResolver.soliditySha3(...[fromHash, toHash].sort()); cryptoInfo = cryptor.getCryptoInfo(combinedHash); if (key) { contentKey = key; @@ -883,22 +944,24 @@ export class ServiceContract extends BaseContract { } else { // group message, scoped to contract cryptoInfo = cryptor.getCryptoInfo( - this.options.nameResolver.soliditySha3(contract.options.address)); + this.options.nameResolver.soliditySha3(contract.options.address), + ); if (key) { // encrpted with calls data key from argument contentKey = key; } else { // retrieve calls data key from sharings contentKey = await this.options.sharing.getKey( - contract.options.address, from, propertyName, block); + contract.options.address, from, propertyName, block, + ); } } if (!contentKey) { - throw new Error(`no content key found for contract "${contract.options.address}" ` + - `and account "${from}"`); + throw new Error(`no content key found for contract "${contract.options.address}" ` + + `and account "${from}"`); } // encrypt with content key - const encryptedBuffer = await cryptor.encrypt(toEncrypt, { key: contentKey, }); + const encryptedBuffer = await cryptor.encrypt(toEncryptParameter, { key: contentKey }); const encrypted = encryptedBuffer.toString(this.encodingEncrypted); const envelope: Envelope = { private: encrypted, @@ -919,7 +982,11 @@ export class ServiceContract extends BaseContract { * @return {Promise} encrypted envelope or hash as string */ private async encryptHash( - toEncrypt: string, contract: any, accountId: string, key?: Buffer): Promise { + toEncrypt: string, + contract: any, + accountId: string, + key?: Buffer, + ): Promise { // get hashKkey from contract let hashKey; if (key) { @@ -929,13 +996,14 @@ export class ServiceContract extends BaseContract { } if (!hashKey) { - throw new Error(`no hashKey found for contract "${contract.options.address}" and account ` + - `"${accountId}"`); + throw new Error(`no hashKey found for contract "${contract.options.address}" and account ` + + `"${accountId}"`); } // encrypt with hashKkey const cryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo(this.cryptoAlgorithHashes); const encryptedBuffer = await cryptor.encrypt( - Buffer.from(toEncrypt.substr(2), this.encodingUnencryptedHash), { key: hashKey, }); + Buffer.from(toEncrypt.substr(2), this.encodingUnencryptedHash), { key: hashKey }, + ); return `0x${encryptedBuffer.toString(this.encodingEncrypted)}`; } @@ -951,13 +1019,14 @@ export class ServiceContract extends BaseContract { * @return {Promise} object with result info */ private async getEntries( - serviceContract: any, - type = 'calls' || 'answers', - callId?: number, - count = 10, - offset = 0, - reverse = false): Promise { - let result = { + serviceContract: any, + type = 'calls' || 'answers', + callId?: number, + count = 10, + offset = 0, + reverse = false, + ): Promise { + const result = { entries: {}, indices: [], }; @@ -967,8 +1036,8 @@ export class ServiceContract extends BaseContract { let entryCount; let queryOffset = offset; if (reverse) { - entryCount = await (type === 'calls' ? - this.getCallCount(serviceContract) : this.getAnswerCount(serviceContract, callId)); + entryCount = await (type === 'calls' + ? this.getCallCount(serviceContract) : this.getAnswerCount(serviceContract, callId)); queryOffset = Math.max(entryCount - offset - count, 0); } let itemsRetrieved = 0; @@ -977,14 +1046,16 @@ export class ServiceContract extends BaseContract { let queryResult; if (type === 'calls') { queryResult = await this.options.executor.executeContractCall( - serviceContract, 'getCalls', singleQueryOffset); + serviceContract, 'getCalls', singleQueryOffset, + ); } else { queryResult = await this.options.executor.executeContractCall( - serviceContract, 'getAnswers', callId, singleQueryOffset); + serviceContract, 'getAnswers', callId, singleQueryOffset, + ); } itemsRetrieved += resultsPerPage; - for (let i = 0; i < queryResult.hash.length; i++) { + for (let i = 0; i < queryResult.hash.length; i += 1) { const resultId = i + singleQueryOffset; result.indices.push(resultId); result.entries[resultId] = {}; diff --git a/src/contracts/sharing.spec.ts b/src/contracts/sharing.spec.ts index ffb9a3cb..4fe5d18c 100644 --- a/src/contracts/sharing.spec.ts +++ b/src/contracts/sharing.spec.ts @@ -19,7 +19,7 @@ import 'mocha'; import { expect, use } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); +import * as chaiAsPromised from 'chai-as-promised'; import { DfsInterface, @@ -28,7 +28,6 @@ import { } from '@evan.network/dbcp'; import { accounts } from '../test/accounts'; -import { configTestcore as config } from '../config-testcore'; import { CryptoProvider } from '../encryption/crypto-provider'; import { sampleContext, TestUtils } from '../test/test-utils'; import { Sharing } from './sharing'; @@ -36,7 +35,7 @@ import { Sharing } from './sharing'; use(chaiAsPromised); -describe('Sharing handler', function() { +describe('Sharing handler', function test() { this.timeout(300000); let executor: Executor; let dfs: DfsInterface; @@ -60,20 +59,21 @@ describe('Sharing handler', function() { executor, dfs, keyProvider: TestUtils.getKeyProvider(), - nameResolver: nameResolver, + nameResolver, defaultCryptoAlgo: 'aes', }); }); function runContractTests(isMultiShared) { const contractName = !isMultiShared ? 'Shared' : 'MultiSharedTest'; - const sharingId = !isMultiShared ? - null : `0x${Math.floor(Math.random() * 255 * 255 * 255).toString(16).padStart(64, '0')}`; + const sharingId = !isMultiShared + ? null : `0x${Math.floor(Math.random() * 255 * 255 * 255).toString(16).padStart(64, '0')}`; it('should be able to add a sharing', async () => { const randomSecret = `super secret; ${Math.random()}`; const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); await sharing.addSharing( contract.options.address, accounts[0], @@ -90,7 +90,8 @@ describe('Sharing handler', function() { it('should be able to get a sharing key', async () => { const randomSecret = `super secret; ${Math.random()}`; const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); await sharing.addSharing( contract.options.address, accounts[0], @@ -109,7 +110,8 @@ describe('Sharing handler', function() { it('should be able to list all sharings', async () => { const randomSecret = `super secret; ${Math.random()}`; const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); await sharing.addSharing(contract.options.address, accounts[0], accounts[1], @@ -118,17 +120,18 @@ describe('Sharing handler', function() { randomSecret, null, false, - sharingId, - ); + sharingId); const sharings = await sharing.getSharings( - contract.options.address, null, null, null, sharingId); + contract.options.address, null, null, null, sharingId, + ); expect(sharings).not.to.be.undefined; }); it('should be able to remove a sharing', async () => { const randomSecret = `super secret; ${Math.random()}`; const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); await sharing.addSharing( contract.options.address, accounts[0], @@ -141,18 +144,22 @@ describe('Sharing handler', function() { sharingId, ); let sharings = await sharing.getSharings( - contract.options.address, null, null, null, sharingId); + contract.options.address, null, null, null, sharingId, + ); expect(Object.keys(sharings).length).to.eq(1); expect(Object.keys(sharings[nameResolver.soliditySha3(accounts[1])]).length).to.eq(1); expect( Object.keys( - sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('*')]).length) + sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('*')], + ).length, + ) .to.eq(1); expect(sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('*')][0]) .to.eq(randomSecret); await sharing.removeSharing( - contract.options.address, accounts[0], accounts[1], '*', sharingId); + contract.options.address, accounts[0], accounts[1], '*', sharingId, + ); sharings = await sharing.getSharings(contract.options.address, null, null, null, sharingId); expect(Object.keys(sharings).length).to.eq(0); const key1 = await sharing.getKey(contract.options.address, accounts[1], '*', 0, sharingId); @@ -162,7 +169,8 @@ describe('Sharing handler', function() { it('should be able to store sharings under a given context', async () => { const randomSecret = `super secret; ${Math.random()}`; const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); await sharing.addSharing( contract.options.address, accounts[0], @@ -178,7 +186,8 @@ describe('Sharing handler', function() { expect(key).to.eq(randomSecret); const contract2 = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); const unknownContext = 'I have not been added to any config'; let err; try { @@ -194,7 +203,8 @@ describe('Sharing handler', function() { sharingId, ); await sharing.getKey( - contract2.options.address, accounts[1], '*', 0, sharingId); + contract2.options.address, accounts[1], '*', 0, sharingId, + ); } catch (ex) { err = ex; } @@ -208,7 +218,8 @@ describe('Sharing handler', function() { `super secret; ${Math.random()}`, ]; const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); await sharing.addSharing( contract.options.address, accounts[0], @@ -242,28 +253,35 @@ describe('Sharing handler', function() { false, sharingId, ); - let sharings = await sharing.getSharings( - contract.options.address, null, null, null, sharingId); + const sharings = await sharing.getSharings( + contract.options.address, null, null, null, sharingId, + ); // object checks expect(Object.keys(sharings)).to.deep.eq([nameResolver.soliditySha3(accounts[1])]); expect( - sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]) - .to.deep.eq({ '0': randomSecret[0], }); + sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')], + ) + .to.deep.eq({ 0: randomSecret[0] }); expect( - sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionTwo')]) - .to.deep.eq({ '0': randomSecret[1], }); + sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionTwo')], + ) + .to.deep.eq({ 0: randomSecret[1] }); expect( - sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionThree')]) - .to.deep.eq({ '0': randomSecret[2], }); + sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionThree')], + ) + .to.deep.eq({ 0: randomSecret[2] }); // getKey checks expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 1000, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 1000, sharingId), + ) .to.eq(randomSecret[0]); expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionTwo', 1000, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionTwo', 1000, sharingId), + ) .to.eq(randomSecret[1]); expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionThree', 1000, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionThree', 1000, sharingId), + ) .to.eq(randomSecret[2]); }); @@ -273,7 +291,8 @@ describe('Sharing handler', function() { `super secret; ${Math.random()}`, ]; const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); await sharing.addSharing( contract.options.address, accounts[0], @@ -296,42 +315,49 @@ describe('Sharing handler', function() { false, sharingId, ); - let sharings = await sharing.getSharings( - contract.options.address, null, null, null, sharingId); + const sharings = await sharing.getSharings( + contract.options.address, null, null, null, sharingId, + ); // object checks expect(Object.keys(sharings)).to.deep.eq([nameResolver.soliditySha3(accounts[1])]); expect( - sharings - [nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]['100']) + sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]['100'], + ) .to.eq(randomSecret[0]); expect( - sharings - [nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]['200']) + sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]['200'], + ) .to.eq(randomSecret[1]); // getKey checks // exactly in block 0 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 0, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 0, sharingId), + ) .to.eq(undefined); // between before block 100 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 12, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 12, sharingId), + ) .to.eq(undefined); // exactly in block 100 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 100, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 100, sharingId), + ) .to.eq(randomSecret[0]); // between block 100 and block 200 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 123, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 123, sharingId), + ) .to.eq(randomSecret[0]); // exactly in block 200 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 200, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 200, sharingId), + ) .to.eq(randomSecret[1]); // after block 200 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 234, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 234, sharingId), + ) .to.eq(randomSecret[1]); }); @@ -341,7 +367,8 @@ describe('Sharing handler', function() { `super secret; ${Math.random()}`, ]; const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); await sharing.addSharing( contract.options.address, accounts[0], @@ -364,25 +391,32 @@ describe('Sharing handler', function() { false, sharingId, ); - let sharings = await sharing.getSharings( - contract.options.address, null, null, null, sharingId); + const sharings = await sharing.getSharings( + contract.options.address, null, null, null, sharingId, + ); // object checks expect(Object.keys(sharings).sort()) .to.deep.eq( - [nameResolver.soliditySha3(accounts[1]), nameResolver.soliditySha3(accounts[0])].sort()); + [nameResolver.soliditySha3(accounts[1]), nameResolver.soliditySha3(accounts[0])].sort(), + ); expect( - sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]) + sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')], + ) .to.deep.eq( - { '0': randomSecret[0], }); + { 0: randomSecret[0] }, + ); expect( - sharings[nameResolver.soliditySha3(accounts[0])][nameResolver.soliditySha3('sectionOne')]) - .to.deep.eq({ '0': randomSecret[1], }); + sharings[nameResolver.soliditySha3(accounts[0])][nameResolver.soliditySha3('sectionOne')], + ) + .to.deep.eq({ 0: randomSecret[1] }); // getKey checks expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 0, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 0, sharingId), + ) .to.eq(randomSecret[0]); expect( - await sharing.getKey(contract.options.address, accounts[0], 'sectionOne', 0, sharingId)) + await sharing.getKey(contract.options.address, accounts[0], 'sectionOne', 0, sharingId), + ) .to.eq(randomSecret[1]); }); @@ -392,7 +426,8 @@ describe('Sharing handler', function() { `super secret; ${Math.random()}`, ]; const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); await sharing.addSharing( contract.options.address, accounts[0], @@ -405,41 +440,48 @@ describe('Sharing handler', function() { sharingId, ); let sharings = await sharing.getSharings( - contract.options.address, null, null, null, sharingId); + contract.options.address, null, null, null, sharingId, + ); // object checks expect(Object.keys(sharings)).to.deep.eq([nameResolver.soliditySha3(accounts[1])]); expect( - sharings - [nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]['100']) + sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]['100'], + ) .to.eq(randomSecret[0]); expect( - sharings - [nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]['200']) + sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]['200'], + ) .to.eq(undefined); // initial keys setup // exactly in block 0 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 0, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 0, sharingId), + ) .to.eq(undefined); // between before block 100 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 12, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 12, sharingId), + ) .to.eq(undefined); // exactly in block 100 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 100, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 100, sharingId), + ) .to.eq(randomSecret[0]); // between block 100 and block 200 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 123, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 123, sharingId), + ) .to.eq(randomSecret[0]); // exactly in block 200 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 200, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 200, sharingId), + ) .to.eq(randomSecret[0]); // after block 200 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 234, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 234, sharingId), + ) .to.eq(randomSecret[0]); // add new key, valid in and after block 200 @@ -458,36 +500,42 @@ describe('Sharing handler', function() { sharings = await sharing.getSharings(contract.options.address, null, null, null, sharingId); expect(Object.keys(sharings)).to.deep.eq([nameResolver.soliditySha3(accounts[1])]); expect( - sharings - [nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]['100']) + sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]['100'], + ) .to.eq(randomSecret[0]); expect( - sharings - [nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]['200']) + sharings[nameResolver.soliditySha3(accounts[1])][nameResolver.soliditySha3('sectionOne')]['200'], + ) .to.eq(randomSecret[1]); // exactly in block 0 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 0, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 0, sharingId), + ) .to.eq(undefined); // between before block 100 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 12, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 12, sharingId), + ) .to.eq(undefined); // exactly in block 100 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 100, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 100, sharingId), + ) .to.eq(randomSecret[0]); // between block 100 and block 200 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 123, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 123, sharingId), + ) .to.eq(randomSecret[0]); // exactly in block 200 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 200, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 200, sharingId), + ) .to.eq(randomSecret[1]); // after block 200 expect( - await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 234, sharingId)) + await sharing.getKey(contract.options.address, accounts[1], 'sectionOne', 234, sharingId), + ) .to.eq(randomSecret[1]); }); @@ -498,11 +546,14 @@ describe('Sharing handler', function() { `super secret; ${Math.random()}`, ]; const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); // if no sharings added, key is undefined expect( await sharing.getKey( - contract.options.address, accounts[1], 'sectionOne', Number.MAX_SAFE_INTEGER, sharingId)) + contract.options.address, accounts[1], 'sectionOne', Number.MAX_SAFE_INTEGER, sharingId, + ), + ) .to.eq(undefined); // add a key, this will be the latest key await sharing.addSharing( @@ -518,7 +569,9 @@ describe('Sharing handler', function() { ); expect( await sharing.getKey( - contract.options.address, accounts[1], 'sectionOne', Number.MAX_SAFE_INTEGER, sharingId)) + contract.options.address, accounts[1], 'sectionOne', Number.MAX_SAFE_INTEGER, sharingId, + ), + ) .to.eq(randomSecret[0]); // add a key before the first one --> latest key should not change await sharing.addSharing( @@ -534,7 +587,9 @@ describe('Sharing handler', function() { ); expect( await sharing.getKey( - contract.options.address, accounts[1], 'sectionOne', Number.MAX_SAFE_INTEGER, sharingId)) + contract.options.address, accounts[1], 'sectionOne', Number.MAX_SAFE_INTEGER, sharingId, + ), + ) .to.eq(randomSecret[0]); // add a key after the first one --> latest key should change await sharing.addSharing( @@ -550,30 +605,37 @@ describe('Sharing handler', function() { ); expect( await sharing.getKey( - contract.options.address, accounts[1], 'sectionOne', Number.MAX_SAFE_INTEGER, sharingId)) + contract.options.address, accounts[1], 'sectionOne', Number.MAX_SAFE_INTEGER, sharingId, + ), + ) .to.eq(randomSecret[2]); }); it('should be able to share hash keys', async () => { const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); const hashCryptor = cryptoProvider.getCryptorByCryptoAlgo('aesEcb'); const hashKey = await hashCryptor.generateKey(); await sharing.ensureHashKey( - contract.options.address, accounts[0], accounts[0], hashKey, null, sharingId); + contract.options.address, accounts[0], accounts[0], hashKey, null, sharingId, + ); await sharing.ensureHashKey( - contract.options.address, accounts[0], accounts[1], hashKey, null, sharingId); + contract.options.address, accounts[0], accounts[1], hashKey, null, sharingId, + ); const key = await sharing.getHashKey(contract.options.address, accounts[1], sharingId); expect(key).to.eq(hashKey); }); it('should be able to share hash implicitely when sharing other keys', async () => { const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); const hashCryptor = cryptoProvider.getCryptorByCryptoAlgo('aesEcb'); const hashKey = await hashCryptor.generateKey(); await sharing.ensureHashKey( - contract.options.address, accounts[0], accounts[0], hashKey, null, sharingId); + contract.options.address, accounts[0], accounts[0], hashKey, null, sharingId, + ); // await sharing.ensureHashKey(contract.options.address, accounts[0], accounts[1], hashKey); const randomSecret = `super secret; ${Math.random()}`; await sharing.addSharing( @@ -589,11 +651,13 @@ describe('Sharing handler', function() { ); // check shared key const sharedKey = await sharing.getKey( - contract.options.address, accounts[1], '*', 0, sharingId); + contract.options.address, accounts[1], '*', 0, sharingId, + ); expect(sharedKey).to.eq(randomSecret); // check hash key const hashKeyRetrieved = await sharing.getHashKey( - contract.options.address, accounts[1], sharingId); + contract.options.address, accounts[1], sharingId, + ); expect(hashKeyRetrieved).to.eq(hashKey); }); @@ -607,7 +671,8 @@ describe('Sharing handler', function() { 200: randomSecret[1], }; const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); await sharing.addSharing( contract.options.address, accounts[0], @@ -631,7 +696,8 @@ describe('Sharing handler', function() { sharingId, ); const history = await sharing.getKeyHistory( - contract.options.address, accounts[1], 'sectionOne', sharingId); + contract.options.address, accounts[1], 'sectionOne', sharingId, + ); expect(history).to.deep.eq(target); }); @@ -641,7 +707,8 @@ describe('Sharing handler', function() { const randomSecret = `super secret; ${Math.random()}`; // create a contract with a sharing const contract = await executor.createContract( - contractName, [], { from: accounts[0], gas: 500000, }); + contractName, [], { from: accounts[0], gas: 500000 }, + ); await sharing.addSharing( contract.options.address, accounts[0], @@ -662,26 +729,28 @@ describe('Sharing handler', function() { executor, dfs, keyProvider: TestUtils.getKeyProvider(), - nameResolver: nameResolver, + nameResolver, defaultCryptoAlgo: 'aes', }); // add sharing key - const sharingHash = await (sharingId ? - executor.executeContractCall(contract, 'multiSharings', sharingId) : - executor.executeContractCall(contract, 'sharing') + const sharingHash = await (sharingId + ? executor.executeContractCall(contract, 'multiSharings', sharingId) + : executor.executeContractCall(contract, 'sharing') ); const sharings = await dfs.get(sharingHash); newSharing.addHashToCache( - contract.options.address, JSON.parse(sharings.toString()), sharingId); + contract.options.address, JSON.parse(sharings.toString()), sharingId, + ); const key = await newSharing.getKey( - contract.options.address, accounts[1], '*', 0, sharingId); + contract.options.address, accounts[1], '*', 0, sharingId, + ); expect(key).to.eq(randomSecret); }); }); } - describe('for contracts that inherit from "Shared"', function() { + describe('for contracts that inherit from "Shared"', () => { runContractTests(false); it('should be able to bump keys (add a new key for given section to all given accounts', @@ -691,7 +760,8 @@ describe('Sharing handler', function() { `super secret; ${Math.random()}`, ]; const contract = await executor.createContract( - 'Shared', [], { from: accounts[0], gas: 500000, }); + 'Shared', [], { from: accounts[0], gas: 500000 }, + ); await sharing.addSharing( contract.options.address, accounts[0], @@ -718,7 +788,7 @@ describe('Sharing handler', function() { await sharing.bumpSharings( contract.options.address, accounts[0], - [ accounts[1] ], + [accounts[1]], 'sectionOne', block, bumpKey, @@ -730,17 +800,19 @@ describe('Sharing handler', function() { }); }); - describe('for contracts that inherit from "MultiShared"', function() { + describe('for contracts that inherit from "MultiShared"', () => { runContractTests(true); it('can manage multiple sharings autonomously', async () => { const count = 3; const sharingIds = [...Array(count)].map( - () => `0x${Math.floor(Math.random() * 255 * 255 * 255).toString(16).padStart(64, '0')}`); + () => `0x${Math.floor(Math.random() * 255 * 255 * 255).toString(16).padStart(64, '0')}`, + ); const randomSecrets = [...Array(count)].map(() => `super secret; ${Math.random()}`); const contract = await executor.createContract( - 'MultiSharedTest', [], { from: accounts[0], gas: 500000, }); - for (let i = 0; i < count; i++) { + 'MultiSharedTest', [], { from: accounts[0], gas: 500000 }, + ); + for (let i = 0; i < count; i += 1) { await sharing.addSharing( contract.options.address, accounts[0], @@ -753,9 +825,10 @@ describe('Sharing handler', function() { sharingIds[i], ); } - for (let i = 0; i < count; i++) { + for (let i = 0; i < count; i += 1) { const key = await sharing.getKey( - contract.options.address, accounts[1], '*', 0, sharingIds[i]); + contract.options.address, accounts[1], '*', 0, sharingIds[i], + ); expect(key).to.eq(randomSecrets[i]); } }); diff --git a/src/contracts/sharing.ts b/src/contracts/sharing.ts index 0806578a..c1929cd6 100644 --- a/src/contracts/sharing.ts +++ b/src/contracts/sharing.ts @@ -19,8 +19,6 @@ import { ContractLoader, - Cryptor, - Description, Executor, DfsInterface, KeyProvider, @@ -29,7 +27,10 @@ import { LoggerOptions, } from '@evan.network/dbcp'; -import { CryptoProvider } from '../encryption/crypto-provider'; +import { + CryptoProvider, + Description, +} from '../index'; // constant hash: this.options.nameResolver.soliditySha3('*') @@ -52,18 +53,19 @@ export interface SharingOptions extends LoggerOptions { * @class Sharing (name) */ export class Sharing extends Logger { - options: SharingOptions; + public options: SharingOptions; private readonly encodingUnencrypted = 'binary'; + private readonly encodingEncrypted = 'hex'; + private sharingCache = {}; + private hashCache = {}; - constructor(options: SharingOptions) { + public constructor(options: SharingOptions) { super(options); - this.options = Object.assign({ - defaultCryptoAlgo: 'aes', - }, options); + this.options = { defaultCryptoAlgo: 'aes', ...options }; } /** @@ -81,19 +83,18 @@ export class Sharing extends Logger { * @return {Promise} resolved when done */ public async addSharing( - address: string, - originator: string, - partner: string, - section: string, - block: number|string, - sharingKey: string, - context?: string, - isHashKey = false, - sharingId: string = null, - ): Promise { + address: string, + originator: string, + partner: string, + section: string, + block: number|string, + sharingKey: string, + context?: string, + isHashKey = false, + sharingId: string = null, + ): Promise { let sharings; let contract; - let description; // load if (address.startsWith('0x')) { @@ -109,7 +110,9 @@ export class Sharing extends Logger { } // extend sharings - sharings = await this.extendSharings(sharings, originator, partner, section, block, sharingKey, context); + sharings = await this.extendSharings( + sharings, originator, partner, section, block, sharingKey, context, + ); // if not already sharing a hash key if (!isHashKey) { @@ -161,13 +164,13 @@ export class Sharing extends Logger { * @return {Promise} resolved when done */ public async bumpSharings( - address: string, - originator: string, - partners: string[], - section: string, - block: number, - sharingKey: string, - ): Promise { + address: string, + originator: string, + partners: string[], + section: string, + block: number, + sharingKey: string, + ): Promise { let sharings; let contract; let description; @@ -195,8 +198,15 @@ export class Sharing extends Logger { } // add new keys - for (let partner of partners) { - sharings = await this.extendSharings(sharings, originator, partner, section, block, sharingKey); + for (const partner of partners) { + sharings = await this.extendSharings( + sharings, + originator, + partner, + section, + block, + sharingKey, + ); } // save updated sharings @@ -230,8 +240,15 @@ export class Sharing extends Logger { * @param {string} sharingId id of a sharing (when multi-sharings is used) * @return {Promise} resolved when done */ + // eslint-disable-next-line consistent-return public async ensureHashKey( - address: string, originator: string, partner: string, hashKey: string, context?: string, sharingId: string = null): Promise { + address: string, + originator: string, + partner: string, + hashKey: string, + context?: string, + sharingId: string = null, + ): Promise { const setHashKey = await this.getHashKey(address, partner, sharingId); if (!setHashKey) { return this.addSharing(address, originator, partner, '*', 'hashKey', hashKey, context, true, sharingId); @@ -252,34 +269,39 @@ export class Sharing extends Logger { * @return {Promise} updated sharings info */ public async extendSharings( - sharings: any, - originator: string, - partner: string, - section: string, - block: number|string, - sharingKey: string, - context?: string): Promise { + sharings: any, + originator: string, + partner: string, + section: string, + block: number|string, + sharingKey: string, + context?: string, + ): Promise { // encrypt sharing key const originatorHash = this.options.nameResolver.soliditySha3(originator); const partnerHash = this.options.nameResolver.soliditySha3(partner); const sectionHash = this.options.nameResolver.soliditySha3(section); - const edgeKey = context ? this.options.nameResolver.soliditySha3(context) : - this.options.nameResolver.soliditySha3.apply(this.options.nameResolver, [originatorHash, partnerHash].sort()); - const cryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo(this.options.defaultCryptoAlgo); + const edgeKey = context ? this.options.nameResolver.soliditySha3(context) + : this.options.nameResolver.soliditySha3(...[originatorHash, partnerHash].sort()); + const cryptor = this.options.cryptoProvider.getCryptorByCryptoAlgo( + this.options.defaultCryptoAlgo, + ); const cryptoInfo = cryptor.getCryptoInfo(edgeKey); const encryptionKey = await this.options.keyProvider.getKey(cryptoInfo); if (!encryptionKey) { - throw new Error(`could not extend sharings, no key found for "${originatorHash}" to "${partnerHash}"` + - `${context ? (' in context "' + context + '"') : ''}`); + throw new Error(`could not extend sharings, no key found for "${originatorHash}" to "${partnerHash}"` + + `${context ? (` in context "${context}"`) : ''}`); } - const encryptedBuffer = await cryptor.encrypt(sharingKey, { key: encryptionKey, }); - sharings[partnerHash] = sharings[partnerHash] ? sharings[partnerHash] : {}; - sharings[partnerHash][sectionHash] = sharings[partnerHash][sectionHash] ? sharings[partnerHash][sectionHash] : {}; - sharings[partnerHash][sectionHash][block] = { + const encryptedBuffer = await cryptor.encrypt(sharingKey, { key: encryptionKey }); + const sharingsparam = sharings; + sharingsparam[partnerHash] = sharingsparam[partnerHash] ? sharingsparam[partnerHash] : {}; + sharingsparam[partnerHash][sectionHash] = sharingsparam[partnerHash][sectionHash] + ? sharingsparam[partnerHash][sectionHash] : {}; + sharingsparam[partnerHash][sectionHash][block] = { private: encryptedBuffer.toString(this.encodingEncrypted), cryptoInfo, }; - return sharings; + return sharingsparam; } /** @@ -290,7 +312,11 @@ export class Sharing extends Logger { * @param {string} sharingId id of a sharing (when multi-sharings is used) * @return {Promise} matching key */ - public async getHashKey(address: string, partner: string, sharingId: string = null): Promise { + public async getHashKey( + address: string, + partner: string, + sharingId: string = null, + ): Promise { return this.getKey(address, partner, '*', 'hashKey', sharingId); } @@ -303,7 +329,13 @@ export class Sharing extends Logger { * @param {number} _block starting with this block, the key is valid * @return {Promise} sharings as an object. */ - public async getSharings(address: string, _partner?: string, _section?: string, _block?: number, sharingId: string = null): Promise { + public async getSharings( + address: string, + _partner?: string, + _section?: string, + _block?: number, + sharingId: string = null, + ): Promise { let sharings; if (address.startsWith('0x')) { // encrypted sharings from contract @@ -334,13 +366,11 @@ export class Sharing extends Logger { * @return {Promise} sharings object */ public async getSharingsFromContract(contract: any, sharingId: string = null): Promise { - let result = {}; - let sharings; - // use preloaded hashes if available - if (!this.hashCache[contract.options.address] || - !this.hashCache[contract.options.address][sharingId] || - this.hashCache[contract.options.address][sharingId] === '0x0000000000000000000000000000000000000000000000000000000000000000') { + if (!this.hashCache[contract.options.address] + || !this.hashCache[contract.options.address][sharingId] + || this.hashCache[contract.options.address][sharingId] + === '0x0000000000000000000000000000000000000000000000000000000000000000') { if (!this.hashCache[contract.options.address]) { this.hashCache[contract.options.address] = {}; } @@ -348,11 +378,9 @@ export class Sharing extends Logger { let sharingHash; if (sharingId) { - sharingHash = - await this.options.executor.executeContractCall(contract, 'multiSharings', sharingId); + sharingHash = await this.options.executor.executeContractCall(contract, 'multiSharings', sharingId); } else { - sharingHash = - await this.options.executor.executeContractCall(contract, 'sharing'); + sharingHash = await this.options.executor.executeContractCall(contract, 'sharing'); } let sharingValue; @@ -384,18 +412,19 @@ export class Sharing extends Logger { * @return {Promise} matching key */ public async getKey( - address: string, - partner: string, - section: string, - block: number|string = Number.MAX_SAFE_INTEGER, - sharingId: string = null): Promise { + address: string, + partner: string, + section: string, + block: number|string = Number.MAX_SAFE_INTEGER, + sharingId: string = null, + ): Promise { const partnerHash = this.options.nameResolver.soliditySha3(partner); const sectionHash = this.options.nameResolver.soliditySha3(section || '*'); - if (!this.sharingCache[address] || - !this.sharingCache[address][sharingId] || - !this.sharingCache[address][sharingId][partnerHash] || - !this.sharingCache[address][sharingId][partnerHash][sectionHash] || - !this.sharingCache[address][sharingId][partnerHash][sectionHash][block]) { + if (!this.sharingCache[address] + || !this.sharingCache[address][sharingId] + || !this.sharingCache[address][sharingId][partnerHash] + || !this.sharingCache[address][sharingId][partnerHash][sectionHash] + || !this.sharingCache[address][sharingId][partnerHash][sectionHash][block]) { const sharingsValue = await this.getSharings(address, null, null, null, sharingId); if (!this.sharingCache[address]) { this.sharingCache[address] = {}; @@ -419,12 +448,13 @@ export class Sharing extends Logger { } if (typeof block === 'number') { // look for matching block - // the block key, that was set last before encrypting (encryption happened after block ${block}) - const blocks = Object.keys(sectionBlocks).map(blockNr => parseInt(blockNr, 10)); - const lteBlocks = blocks.filter(blockNr => blockNr <= block); + // the block key, that was set last before encrypting + // (encryption happened after block ${block}) + const blocks = Object.keys(sectionBlocks).map((blockNr) => parseInt(blockNr, 10)); + const lteBlocks = blocks.filter((blockNr) => blockNr <= block); if (!lteBlocks.length) { - this.log(`could not find key for contract "${address}" and partner "${partner}" ` + - `for section "${section}", that is new enough`, 'debug'); + this.log(`could not find key for contract "${address}" and partner "${partner}" ` + + `for section "${section}", that is new enough`, 'debug'); } else { // oldest key block, that is younger than the context block const matchingBlock = lteBlocks[lteBlocks.length - 1]; @@ -434,8 +464,8 @@ export class Sharing extends Logger { // use drectly matching block (e.g. for 'hashKey') return sectionBlocks[block]; } - } + return undefined; } /** @@ -447,15 +477,19 @@ export class Sharing extends Logger { * @param {string} sharingId id of a sharing (when multi-sharings is used) * @return {Promise} object with key: blockNr, value: key */ - public async getKeyHistory(address: string, partner: string, section: string, sharingId: string = null): Promise { + public async getKeyHistory( + address: string, + partner: string, + section: string, + sharingId: string = null, + ): Promise { const sharings = await this.getSharings(address, partner, section, null, sharingId); const partnetHash = this.options.nameResolver.soliditySha3(partner); const sectiontHash = this.options.nameResolver.soliditySha3(section); if (sharings && sharings[partnetHash] && sharings[partnetHash][sectiontHash]) { return sharings[partnetHash][sectiontHash]; - } else { - return null; } + return null; } /** @@ -469,11 +503,12 @@ export class Sharing extends Logger { * @return {Promise} resolved when done */ public async removeSharing( - address: string, - originator: string, - partner: string, - section: string, - sharingId: string = null): Promise { + address: string, + originator: string, + partner: string, + section: string, + sharingId: string = null, + ): Promise { const partnerHash = this.options.nameResolver.soliditySha3(partner); const sectionHash = this.options.nameResolver.soliditySha3(section); let sharings; @@ -507,24 +542,28 @@ export class Sharing extends Logger { // delete entry delete sharings[partnerHash][sectionHash]; // remove from cache if already cached - if (this.sharingCache[address] && - this.sharingCache[address][sharingId] && - this.sharingCache[address][sharingId][partnerHash] && - this.sharingCache[address][sharingId][partnerHash][sectionHash]) { + if (this.sharingCache[address] + && this.sharingCache[address][sharingId] + && this.sharingCache[address][sharingId][partnerHash] + && this.sharingCache[address][sharingId][partnerHash][sectionHash]) { delete this.sharingCache[address][sharingId][partnerHash][sectionHash]; - } + } // save if (address.startsWith('0x')) { const updatedHash = await this.options.dfs.add( - 'sharing', Buffer.from(JSON.stringify(sharings), this.encodingUnencrypted)); + 'sharing', Buffer.from(JSON.stringify(sharings), this.encodingUnencrypted), + ); if (sharingId) { await this.options.executor.executeContractTransaction( - contract, 'setMultiSharing', { from: originator, autoGas: 1.1, }, sharingId, updatedHash); + contract, 'setMultiSharing', { from: originator, autoGas: 1.1 }, sharingId, updatedHash, + ); } else { await this.options.executor.executeContractTransaction( - contract, 'setSharing', { from: originator, autoGas: 1.1, }, updatedHash); + contract, 'setSharing', { from: originator, autoGas: 1.1 }, updatedHash, + ); } - if (this.hashCache[contract.options.address] && this.hashCache[contract.options.address][sharingId]) { + if (this.hashCache[contract.options.address] + && this.hashCache[contract.options.address][sharingId]) { delete this.hashCache[contract.options.address][sharingId]; } } else { @@ -545,7 +584,11 @@ export class Sharing extends Logger { * @return {Promise} resolved when done */ public async saveSharingsToContract( - contract: string, sharings: any, originator: string, sharingId?: string): Promise { + contract: string, + sharings: any, + originator: string, + sharingId?: string, + ): Promise { let shareContract; if (typeof contract === 'string') { shareContract = this.options.contractLoader.loadContract(sharingId ? 'MultiShared' : 'Shared', @@ -555,48 +598,49 @@ export class Sharing extends Logger { } // backup previous hash to be able to remove it afterwards - let oldHash + let oldHash; if (sharingId) { oldHash = await this.options.executor.executeContractCall( shareContract, 'multiSharings', - sharingId + sharingId, ); } else { oldHash = await this.options.executor.executeContractCall( shareContract, - 'sharing' + 'sharing', ); } // upload to ipfs and hash const updatedHash = await this.options.dfs.add( - 'sharing', Buffer.from(JSON.stringify(sharings), this.encodingUnencrypted)); + 'sharing', Buffer.from(JSON.stringify(sharings), this.encodingUnencrypted), + ); // save to contract if (sharingId) { // set new hash await this.options.executor.executeContractTransaction(shareContract, 'setMultiSharing', - { from: originator, autoGas: 1.1, }, sharingId, updatedHash); + { from: originator, autoGas: 1.1 }, sharingId, updatedHash); } else { // set new hash await this.options.executor.executeContractTransaction(shareContract, 'setSharing', - { from: originator, autoGas: 1.1, }, updatedHash); + { from: originator, autoGas: 1.1 }, updatedHash); } // remove the old hash - if (oldHash && - oldHash !== '0x0000000000000000000000000000000000000000000000000000000000000000') { + if (oldHash + && oldHash !== '0x0000000000000000000000000000000000000000000000000000000000000000') { try { await this.options.dfs.remove(oldHash); } catch (ex) { - this.log(`could not unpin old sharing hash: ${ ex.message }`, 'warning'); + this.log(`could not unpin old sharing hash: ${ex.message}`, 'warning'); } } // clear the cache - if (this.hashCache[shareContract.options.address] && - this.hashCache[shareContract.options.address][sharingId]) { + if (this.hashCache[shareContract.options.address] + && this.hashCache[shareContract.options.address][sharingId]) { delete this.hashCache[shareContract.options.address][sharingId]; } this.clearCache(); @@ -618,53 +662,66 @@ export class Sharing extends Logger { sharings: any, partner: string, section?: string, - block?: number|string + block?: number|string, ): Promise { + const shaingsParam = sharings; const partnerHash = this.options.nameResolver.soliditySha3(partner); if (!section) { - delete sharings[partnerHash]; - } else if (sharings[partnerHash]) { + delete shaingsParam[partnerHash]; + } else if (shaingsParam[partnerHash]) { const sectionHash = this.options.nameResolver.soliditySha3(section); if (!block) { - delete sharings[partnerHash][sectionHash]; - } else if (sharings[partnerHash][sectionHash] && sharings[partnerHash][sectionHash][block]) { - delete sharings[partnerHash][sectionHash][block]; + delete shaingsParam[partnerHash][sectionHash]; + } else if (shaingsParam[partnerHash][sectionHash] + && shaingsParam[partnerHash][sectionHash][block]) { + delete shaingsParam[partnerHash][sectionHash][block]; } } - return sharings; + return shaingsParam; } - private async decryptSharings(sharings: any, _partner?: string, _section?: string, _block?: number): Promise { - let result = {}; - const _partnerHash = _partner ? this.options.nameResolver.soliditySha3(_partner) : null; - for (let partnerHashKey of Object.keys(sharings)) { - if (_partnerHash && _partnerHash !== partnerHashKey) { + private async decryptSharings( + sharings: any, + partner?: string, + section?: string, + block?: number, + ): Promise { + const result = {}; + const partnerHash = partner ? this.options.nameResolver.soliditySha3(partner) : null; + for (const partnerHashKey of Object.keys(sharings)) { + if (partnerHash && partnerHash !== partnerHashKey) { + // eslint-disable-next-line no-continue continue; } result[partnerHashKey] = {}; - const partner = sharings[partnerHashKey]; - const _sectionHash = _section ? this.options.nameResolver.soliditySha3(_section) : null; - for (let sectionHashKey of Object.keys(partner)) { - if (_sectionHash && _sectionHash !== sectionHashKey) { + const partnerElem = sharings[partnerHashKey]; + const sectionHash = section ? this.options.nameResolver.soliditySha3(section) : null; + for (const sectionHashKey of Object.keys(partnerElem)) { + if (sectionHash && sectionHash !== sectionHashKey) { + // eslint-disable-next-line no-continue continue; } result[partnerHashKey][sectionHashKey] = {}; - const section = partner[sectionHashKey]; - for (let blockKey of Object.keys(section)) { - if (_block && _block !== parseInt(blockKey, 10)) { + const sectionElem = partnerElem[sectionHashKey]; + for (const blockKey of Object.keys(sectionElem)) { + if (block && block !== parseInt(blockKey, 10)) { + // eslint-disable-next-line no-continue continue; } - const block = section[blockKey]; - const cryptor = this.options.cryptoProvider.getCryptorByCryptoInfo(block.cryptoInfo); - const decryptKey = await this.options.keyProvider.getKey(block.cryptoInfo); + const blockElem = sectionElem[blockKey]; + const cryptor = this.options.cryptoProvider.getCryptorByCryptoInfo(blockElem.cryptoInfo); + const decryptKey = await this.options.keyProvider.getKey(blockElem.cryptoInfo); if (decryptKey) { const decrypted = await cryptor.decrypt( - Buffer.from(block.private, this.encodingEncrypted), { key: decryptKey, }); - result[partnerHashKey][sectionHashKey][blockKey] = decrypted.toString(this.encodingUnencrypted); + Buffer.from(blockElem.private, this.encodingEncrypted), { key: decryptKey }, + ); + result[partnerHashKey][sectionHashKey][blockKey] = decrypted.toString( + this.encodingUnencrypted, + ); } } if (!Object.keys(result[partnerHashKey][sectionHashKey]).length) { - delete result[partnerHashKey][sectionHashKey] + delete result[partnerHashKey][sectionHashKey]; } } if (!Object.keys(result[partnerHashKey]).length) { diff --git a/src/contracts/signer-identity.spec.ts b/src/contracts/signer-identity.spec.ts index 8afe8ffa..7ebf9054 100644 --- a/src/contracts/signer-identity.spec.ts +++ b/src/contracts/signer-identity.spec.ts @@ -18,38 +18,52 @@ */ import 'mocha'; -import BigNumber = require('bignumber.js'); -import chaiAsPromised = require('chai-as-promised'); +import * as BigNumber from 'bignumber.js'; +import * as chaiAsPromised from 'chai-as-promised'; import { expect, use } from 'chai'; import { ContractLoader, + CryptoInfo, + DfsInterface, Executor, + KeyProvider, SignerInternal, } from '@evan.network/dbcp'; import { accounts } from '../test/accounts'; -import { SignerIdentity } from './signer-identity'; import { TestUtils } from '../test/test-utils'; +import { + CryptoProvider, + Ipfs, + Ipld, + Mailbox, + NameResolver, + Profile, + SignerIdentity, + Verifications, +} from '../index'; use(chaiAsPromised); -describe('signer-identity (identity based signer)', function() { +describe('signer-identity (identity based signer)', function test() { this.timeout(300000); + let contractLoader: ContractLoader; + let dfs: DfsInterface; let executor: Executor; - let signer; - let web3; + let signer: SignerIdentity; + let web3: any; before(async () => { web3 = TestUtils.getWeb3(); const contracts = await TestUtils.getContracts(); - const contractLoader = new ContractLoader({ + contractLoader = new ContractLoader({ contracts, web3, }); - const accountStore = TestUtils.getAccountStore({}); + const accountStore = TestUtils.getAccountStore(); const verifications = await TestUtils.getVerifications(web3, await TestUtils.getIpfs()); const underlyingSigner = new SignerInternal({ accountStore, @@ -64,41 +78,44 @@ describe('signer-identity (identity based signer)', function() { web3, }, { - activeIdentity: await verifications.getIdentityForAccount(accounts[0], true), - underlyingAccount: accounts[0], + activeIdentity: await verifications.getIdentityForAccount(accounts[3], true), + underlyingAccount: accounts[3], underlyingSigner, }, ); executor = new Executor( - { config: { alwaysAutoGasLimit: 1.1 }, signer: signer, web3 }); + { config: { alwaysAutoGasLimit: 1.1 }, signer, web3 }, + ); await executor.init({ eventHub: await TestUtils.getEventHub(web3) }); - }); describe('when making transaction with underlying accountId', () => { it('can create a new contract', async () => { const randomString = Math.floor(Math.random() * 1e12).toString(36); const contract = await executor.createContract( - 'TestContract', [randomString], { from: signer.underlyingAccount, gas: 1e6 }); + 'TestContract', [randomString], { from: signer.underlyingAccount, gas: 1e6 }, + ); expect(await executor.executeContractCall(contract, 'data')).to.eq(randomString); }); it('can create expensive contracts', async () => { const randomString = Math.floor(Math.random() * 1e12).toString(36); const contractPromise = executor.createContract( - 'HugeContract', [randomString], { from: signer.underlyingAccount, gas: 12e6 }); + 'HugeContract', [randomString], { from: signer.underlyingAccount, gas: 12e6 }, + ); await expect(contractPromise).not.to.be.rejected; - expect(await executor.executeContractCall(await contractPromise, 'data') - ).to.eq(randomString); + expect(await executor.executeContractCall(await contractPromise, 'data')).to.eq(randomString); }); it('can make transactions on contracts', async () => { const contract = await executor.createContract( - 'TestContract', [''], { from: signer.underlyingAccount, gas: 1e6 }); + 'TestContract', [''], { from: signer.underlyingAccount, gas: 1e6 }, + ); const randomString = Math.floor(Math.random() * 1e12).toString(36); await executor.executeContractTransaction( - contract, 'setData', { from: signer.underlyingAccount }, randomString); + contract, 'setData', { from: signer.underlyingAccount }, randomString, + ); expect(await executor.executeContractCall(contract, 'data')).to.eq(randomString); }); @@ -106,7 +123,10 @@ describe('signer-identity (identity based signer)', function() { const amountToSend = Math.floor(Math.random() * 1e3); const balanceBefore = new BigNumber(await web3.eth.getBalance(accounts[1])); await executor.executeSend( - { from: signer.underlyingAccount, to: accounts[1], gas: 10e6, value: amountToSend }); + { + from: signer.underlyingAccount, to: accounts[1], gas: 10e6, value: amountToSend, + }, + ); const balanceAfter = new BigNumber(await web3.eth.getBalance(accounts[1])); const diff = balanceAfter.minus(balanceBefore); expect(diff.eq(new BigNumber(amountToSend))).to.be.true; @@ -125,36 +145,41 @@ describe('signer-identity (identity based signer)', function() { it('can create a new contract', async () => { const randomString = Math.floor(Math.random() * 1e12).toString(36); const contract = await executor.createContract( - 'TestContract', [randomString], { from: signer.activeIdentity, gas: 1e6 }); + 'TestContract', [randomString], { from: signer.activeIdentity, gas: 1e6 }, + ); expect(await executor.executeContractCall(contract, 'data')).to.eq(randomString); }); it('can create expensive contracts', async () => { const randomString = Math.floor(Math.random() * 1e12).toString(36); const contractPromise = executor.createContract( - 'HugeContract', [randomString], { from: signer.activeIdentity, gas: 1e6 }); + 'HugeContract', [randomString], { from: signer.activeIdentity, gas: 1e6 }, + ); await expect(contractPromise).not.to.be.rejected; - expect(await executor.executeContractCall(await contractPromise, 'data') - ).to.eq(randomString); + expect(await executor.executeContractCall(await contractPromise, 'data')).to.eq(randomString); }); it('can make transactions on contracts', async () => { const contract = await executor.createContract( - 'TestContract', [''], { from: signer.activeIdentity, gas: 1e6 }); + 'TestContract', [''], { from: signer.activeIdentity, gas: 1e6 }, + ); const randomString = Math.floor(Math.random() * 1e12).toString(36); await executor.executeContractTransaction( - contract, 'setData', { from: signer.activeIdentity }, randomString); + contract, 'setData', { from: signer.activeIdentity }, randomString, + ); expect(await executor.executeContractCall(contract, 'data')).to.eq(randomString); }); it('can make transactions on multiple contracts', async () => { const runOneTest = async () => { const contract = await executor.createContract( - 'TestContract', [''], { from: signer.activeIdentity, gas: 1e6 }); + 'TestContract', [''], { from: signer.activeIdentity, gas: 1e6 }, + ); const randomString = Math.floor(Math.random() * 1e12).toString(36); await executor.executeContractTransaction( - contract, 'setData', { from: signer.activeIdentity }, randomString); + contract, 'setData', { from: signer.activeIdentity }, randomString, + ); expect(await executor.executeContractCall(contract, 'data')).to.eq(randomString); }; await Promise.all([...Array(10)].map(() => runOneTest())); @@ -163,10 +188,12 @@ describe('signer-identity (identity based signer)', function() { it('can execute multiple transactions in parallel', async () => { const runOneTest = async () => { const contract = await executor.createContract( - 'TestContract', [''], { from: signer.activeIdentity, gas: 1e6 }); + 'TestContract', [''], { from: signer.activeIdentity, gas: 1e6 }, + ); const randomString = Math.floor(Math.random() * 1e12).toString(36); await executor.executeContractTransaction( - contract, 'setData', { from: signer.activeIdentity }, randomString); + contract, 'setData', { from: signer.activeIdentity }, randomString, + ); }; await Promise.all([...Array(10)].map(() => runOneTest())); }); @@ -174,7 +201,8 @@ describe('signer-identity (identity based signer)', function() { it('can create multiple contracts in parallel', async () => { const runOneTest = async () => { const contract = await executor.createContract( - 'TestContract', [''], { from: signer.activeIdentity, gas: 1e6 }); + 'TestContract', [''], { from: signer.activeIdentity, gas: 1e6 }, + ); expect(contract).to.be.an('Object'); return contract; }; @@ -186,7 +214,8 @@ describe('signer-identity (identity based signer)', function() { describe('when handling events', () => { it('can handle events in contract transactions', async () => { const contract = await executor.createContract( - 'TestContractEvent', [''], { from: signer.activeIdentity, gas: 1e6 }); + 'TestContractEvent', [''], { from: signer.activeIdentity, gas: 1e6 }, + ); const randomString = Math.floor(Math.random() * 1e12).toString(36); const eventValue = await executor.executeContractTransaction( @@ -208,7 +237,8 @@ describe('signer-identity (identity based signer)', function() { it('can handle events in parallel transactions', async () => { const runOneTest = async () => { const contract = await executor.createContract( - 'TestContractEvent', [''], { from: signer.activeIdentity, gas: 1e6 }); + 'TestContractEvent', [''], { from: signer.activeIdentity, gas: 1e6 }, + ); const randomString = Math.floor(Math.random() * 1e12).toString(36); const eventValue = await executor.executeContractTransaction( @@ -236,7 +266,10 @@ describe('signer-identity (identity based signer)', function() { const amountToSend = Math.floor(Math.random() * 1e3); const balanceBefore = new BigNumber(await web3.eth.getBalance(accounts[1])); await executor.executeSend( - { from: signer.activeIdentity, to: accounts[1], gas: 100e3, value: amountToSend }); + { + from: signer.activeIdentity, to: accounts[1], gas: 100e3, value: amountToSend, + }, + ); const balanceAfter = new BigNumber(await web3.eth.getBalance(accounts[1])); const diff = balanceAfter.minus(balanceBefore); expect(diff.eq(new BigNumber(amountToSend))).to.be.true; @@ -245,12 +278,13 @@ describe('signer-identity (identity based signer)', function() { it('should reject transfer and balance of identity should remain same', async () => { const randomString = Math.floor(Math.random() * 1e12).toString(36); const contract = await executor.createContract( - 'TestContract', [randomString], { from: signer.activeIdentity, gas: 1e6 }); + 'TestContract', [randomString], { from: signer.activeIdentity, gas: 1e6 }, + ); expect(contract).to.be.a('Object'); const amountToSend = Math.floor(Math.random() * 1e3); const balanceBefore = new BigNumber(await web3.eth.getBalance(signer.activeIdentity)); - await expect( executor.executeSend({ + await expect(executor.executeSend({ from: signer.activeIdentity, to: contract.options.address, gas: 100e3, @@ -265,7 +299,8 @@ describe('signer-identity (identity based signer)', function() { async () => { const randomString = Math.floor(Math.random() * 1e12).toString(36); const contract = await executor.createContract( - 'TestContract', [randomString], { from: signer.activeIdentity, gas: 1e6 }); + 'TestContract', [randomString], { from: signer.activeIdentity, gas: 1e6 }, + ); expect(contract).to.be.a('Object'); const amountToSend = Math.floor(Math.random() * 1e3); @@ -281,17 +316,18 @@ describe('signer-identity (identity based signer)', function() { const balanceAfter = new BigNumber(await web3.eth.getBalance(signer.underlyingAccount)); expect(balanceAfter.lt(balanceBefore)).to.be.true; - } - ); + }); it('should transfer funds to contract', async () => { const amountToSend = Math.floor(Math.random() * 1e3); const contract = await executor.createContract( - 'TestContract', [], { from: signer.activeIdentity, gas: 1e6 }); + 'TestContract', [], { from: signer.activeIdentity, gas: 1e6 }, + ); expect(contract).to.be.a('Object'); const balanceBefore = new BigNumber(await web3.eth.getBalance(contract.options.address)); await executor.executeContractTransaction( - contract, 'chargeFunds', { from: signer.activeIdentity, value: amountToSend }); + contract, 'chargeFunds', { from: signer.activeIdentity, value: amountToSend }, + ); const balanceAfter = new BigNumber(await web3.eth.getBalance(contract.options.address)); const diff = balanceAfter.minus(balanceBefore); expect(diff.eq(new BigNumber(amountToSend))).to.be.true; @@ -301,12 +337,13 @@ describe('signer-identity (identity based signer)', function() { it('should reject fund transfer to contract without a fallback function', async () => { const randomString = Math.floor(Math.random() * 1e12).toString(36); const contract = await executor.createContract( - 'TestContract', [randomString], { from: signer.activeIdentity, gas: 1e6 }); + 'TestContract', [randomString], { from: signer.activeIdentity, gas: 1e6 }, + ); expect(contract).to.be.a('Object'); const amountToSend = Math.floor(Math.random() * 1e3); const balanceBefore = new BigNumber(await web3.eth.getBalance(contract.options.address)); - await expect( executor.executeSend({ + await expect(executor.executeSend({ from: signer.activeIdentity, to: contract.options.address, gas: 100e3, @@ -330,19 +367,19 @@ describe('signer-identity (identity based signer)', function() { const balanceAfter = new BigNumber(await web3.eth.getBalance(signer.activeIdentity)); const diff = balanceAfter.minus(balanceBefore); expect(diff.eq(new BigNumber(amountToSend))).to.be.true; - } - ); + }); it('should reject fund transfer to new contract and funds should stay with identity ', async () => { const randomString = Math.floor(Math.random() * 1e12).toString(36); const contract = await executor.createContract( - 'TestContract', [randomString], { from: signer.activeIdentity, gas: 1e6 }); + 'TestContract', [randomString], { from: signer.activeIdentity, gas: 1e6 }, + ); expect(contract).to.be.a('Object'); const amountToSend = Math.floor(Math.random() * 1e3); const balanceBefore = new BigNumber(await web3.eth.getBalance(signer.activeIdentity)); - await expect( executor.executeSend({ + await expect(executor.executeSend({ from: signer.activeIdentity, to: contract.options.address, gas: 100e3, @@ -351,8 +388,7 @@ describe('signer-identity (identity based signer)', function() { const balanceAfter = new BigNumber(await web3.eth.getBalance(signer.activeIdentity)); expect(balanceAfter.eq(balanceBefore)).to.be.true; - } - ); + }); }); describe('when signing messages', () => { @@ -360,8 +396,234 @@ describe('signer-identity (identity based signer)', function() { const randomString = Math.floor(Math.random() * 1e12).toString(36); const signPromise = signer.signMessage(signer.activeIdentity, randomString); await expect(signPromise) - .to.be.rejectedWith('signing messages with identities is not supported'); + .to.be.rejectedWith( + 'signing messages with identities is only supported for \'underlyingAccount\'', + ); + }); + }); + }); + + describe('when dealing with encryption for identity based accounts', async () => { + let cryptoProvider: CryptoProvider; + let identityAddress: string; + let keyProvider: KeyProvider; + let nameResolver: NameResolver; + let profile: Profile; + let verifications: Verifications; + + before(async () => { + web3 = await TestUtils.getWeb3(); + nameResolver = await TestUtils.getNameResolver(web3); + dfs = await TestUtils.getIpfs(); + verifications = await TestUtils.getVerifications(web3, dfs); + cryptoProvider = TestUtils.getCryptoProvider(dfs); + + identityAddress = await verifications.getIdentityForAccount(accounts[3], true); + + const sha9Key = nameResolver.soliditySha3( + ...(await Promise.all( + [identityAddress, identityAddress].map( + (accountId) => nameResolver.soliditySha3(accountId), + ), + )), + ); + keyProvider = await TestUtils.getKeyProvider(); + (keyProvider as any).keys[sha9Key] = '483257531bc9456ea783e44d325f8a384a4b89da81dac00e589409431692f218'; + (keyProvider as any).keys[nameResolver.soliditySha3(identityAddress)] = '483257531bc9456ea783e44d325f8a384a4b89da81dac00e589409431692f218'; + const ipld = new Ipld({ + ipfs: dfs as Ipfs, + keyProvider, + cryptoProvider, + defaultCryptoAlgo: 'aes', + originator: nameResolver.soliditySha3(accounts[3]), + nameResolver, + }); + + // create profile instance, that is bound to identity + const dataContract = await TestUtils.getDataContract(web3, dfs); + (dataContract as any).options.executor = executor; + executor.eventHub = await TestUtils.getEventHub(web3); + profile = new Profile({ + accountId: identityAddress, + contractLoader, + cryptoProvider, + dataContract, + defaultCryptoAlgo: 'aes', + dfs, + description: await TestUtils.getDescription(web3), + executor, + ipld, + nameResolver, + rightsAndRoles: await TestUtils.getRightsAndRoles(web3), + sharing: await TestUtils.getSharing(web3), + }); + + // keep code here as long as identity based profiles cannot be created in a more easy way... + // console.log('sharing') + // const ensName = nameResolver.getDomainName((nameResolver as any).config.domains.profile); + // const address = await nameResolver.getAddress(ensName); + // const indexContract = + // contractLoader.loadContract('ProfileIndexInterface', address); + // const profileContractAddress = await executor.executeContractCall( + // indexContract, 'getProfile', identityAddress, { from: accounts[3], }); + // const profileDataContract = await contractLoader.loadContract( + // 'DataContract', profileContractAddress); + // const sharingsHash = await executor.executeContractCall(profileDataContract, 'sharing'); + // console.dir(sharingsHash); + // const sharingData = await dfs.get(sharingsHash); + // console.log(require('util').inspect(sharingData, { colors: true, depth: 16 })); + // eslint-disable-next-line + // const updatedSharingData = '{"0x2f17103a20c21c65c8f6330761b93af4a72ad7faea76cba6aa8048783e942129":{"0x04994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829":{"hashKey":{"private":"688c841f01db6d2995ea02139bf301cb360362a327294c0e995c2dcf0bf3f530ea04b671b2c4d1f1f289f74dbcade0e3357317a32a89bf5a39f85a70ce81c4c3f4580caf67520bbf99319950f0235b3bcb7a456ee9697f25fc4cc429676c0c48","cryptoInfo":{"originator":"0xd42644616207e5816e2de3ab153837db6148bd036794c06f73763074df41d2b5","keyLength":256,"algorithm":"aes-256-cbc"}}},"0x31c56d8d629ac68792a621f2f85af22618d00b5e5e5f228574590211bde67302":{"335193":{"private":"e5dbbe8f077bd60b7e320e86269e862a8833100bce405a56535350725870cff6dcf00f28a67a45befde886631e09ff722c4060915e6fbe11344e97e433e13b52c7cca0670e0cdfaa47c2cce2d0ac80d16969430c4276f609aa09b9f3dd0eaa67","cryptoInfo":{"originator":"0xd42644616207e5816e2de3ab153837db6148bd036794c06f73763074df41d2b5","keyLength":256,"algorithm":"aes-256-cbc"}}},"0xa05e33768da60583875bb5256189397d790c6a14f448460d366d44805586c6ee":{"335193":{"private":"f027a1585e8c520e4172dd3c9bc7bd92347e633666819fde09ed8483f03be02852c2775a495c32d10374861e8b8781ec3da76bda24264da92c30cf373abb731c238b9f126a0a6193b124be0f55ceb59bad4a78769febd3639dc0bfa7416f71c0","cryptoInfo":{"originator":"0xd42644616207e5816e2de3ab153837db6148bd036794c06f73763074df41d2b5","keyLength":256,"algorithm":"aes-256-cbc"}}}}}'; + // eslint-disable-next-line + // const updatedSharingHash = await dfs.add('sharing', Buffer.from(updatedSharingData, 'utf8')); + // console.log('/sharing') + // await executor.executeContractTransaction( + // profileDataContract, 'setSharing', { from: signer.activeIdentity }, updatedSharingHash); + }); + + it('should be able to encrypt and decrypt data', async () => { + const sampleData = { + foo: TestUtils.getRandomBytes32(), + bar: Math.random(), + }; + + // build edge key for data shared between identity[0] and identity[1] + const keyContext = nameResolver.soliditySha3( + ...(await Promise.all( + [accounts[3], accounts[1]].map( + (accountId) => verifications.getIdentityForAccount(accountId, true), + ), + ) + ).sort(), + ); + const cryptoInfo: CryptoInfo = { + algorithm: 'aes-256-cbc', + block: await web3.eth.getBlockNumber(), + originator: keyContext, + }; + + // generate with custom logic, e.g. with the aes cryptor + const cryptor = cryptoProvider.getCryptorByCryptoAlgo('aes'); + const encryptKey = await cryptor.generateKey(); + + // encrypt files (key is pulled from profile) + const encryptedData: Buffer = await cryptor.encrypt(sampleData, { key: encryptKey }); + const encrypted = { + cryptoInfo, + private: encryptedData.toString('hex'), + }; + + expect(encrypted).to.haveOwnProperty('cryptoInfo'); + expect(encrypted).to.haveOwnProperty('private'); + + const decryptKey = encryptKey; + const decryptedObject = await cryptor.decrypt(Buffer.from(encrypted.private, 'hex'), { key: decryptKey }); + expect(decryptedObject).to.deep.eq(sampleData); + }); + + it('should be able to encrypt and bound to a comm key', async () => { + const sampleData = { + foo: TestUtils.getRandomBytes32(), + bar: Math.random(), + }; + + // build edge key for data shared between identity[0] and identity[1] + const keyContext = nameResolver.soliditySha3( + ...(await Promise.all( + [accounts[3], accounts[1]].map( + (accountId) => verifications.getIdentityForAccount(accountId, true), + ), + ) + ).sort(), + ); + const cryptoInfo: CryptoInfo = { + algorithm: 'aes-256-cbc', + block: await web3.eth.getBlockNumber(), + originator: keyContext, + }; + + // generate with custom logic, e.g. with the aes cryptor + const cryptor = cryptoProvider.getCryptorByCryptoAlgo('aes'); + const encryptKey = await cryptor.generateKey(); + + // store key in profile + const contactIdentity = await verifications.getIdentityForAccount(accounts[1], true); + // profile.activeAccount = accounts[3]; + await profile.loadForAccount(profile.treeLabels.addressBook); + await profile.addContactKey(contactIdentity, 'commKey', encryptKey); + // profile.activeAccount = identityAddress; + await profile.storeForAccount(profile.treeLabels.addressBook); + await profile.loadForAccount(profile.treeLabels.addressBook); + + // encrypt files (key is pulled from profile) + const encryptedData: Buffer = await cryptor.encrypt(sampleData, { key: encryptKey }); + const encrypted = { + cryptoInfo, + private: encryptedData.toString('hex'), + }; + + expect(encrypted).to.haveOwnProperty('cryptoInfo'); + expect(encrypted).to.haveOwnProperty('private'); + + const decryptKey = await profile.getContactKey(contactIdentity, 'commKey'); + const decryptedObject = await cryptor.decrypt(Buffer.from(encrypted.private, 'hex'), { key: decryptKey }); + expect(decryptedObject).to.deep.eq(sampleData); + }); + + it('should be to store data in an identity', async () => { + // generate with custom logic, e.g. with the aes cryptor + const cryptor = cryptoProvider.getCryptorByCryptoAlgo('aes'); + const encryptKey = await cryptor.generateKey(); + + // store key in profile + const contactIdentity = await verifications.getIdentityForAccount(accounts[1], true); + await profile.loadForAccount(profile.treeLabels.addressBook); + await profile.addContactKey(contactIdentity, 'commKey', encryptKey); + await profile.storeForAccount(profile.treeLabels.addressBook); + + // load from on-chain profile + await profile.loadForAccount(profile.treeLabels.addressBook); + const retrieved = await profile.getContactKey(contactIdentity, 'commKey'); + + expect(encryptKey).to.eq(retrieved); + }); + + it('should be able to send a mail', async () => { + const random = Math.random(); + const getTestMail = () => ({ + content: { + from: signer.activeIdentity, + to: signer.activeIdentity, + title: 'talking to myself', + body: `hi, me. I like random numbers, for example ${random}`, + attachments: [ + { + type: 'sharedExchangeKey', + key: '', + }, + ], + }, + }); + const startTime = Date.now(); + const mailbox: Mailbox = new Mailbox({ + contractLoader, + cryptoProvider, + defaultCryptoAlgo: 'aes', + executor, + ipfs: dfs as Ipfs, + keyProvider, + mailboxOwner: signer.activeIdentity, + nameResolver, }); + await mailbox.sendMail(getTestMail(), signer.activeIdentity, signer.activeIdentity); + const result = await mailbox.getMails(1, 0); + const keys = Object.keys(result.mails); + expect(keys.length).to.eq(1); + expect(result.mails[keys[0]].content.sent).to.be.ok; + expect(result.mails[keys[0]].content.sent).to.be.gt(startTime); + expect(result.mails[keys[0]].content.sent).to.be.lt(Date.now()); + delete result.mails[keys[0]].content.sent; + expect(result.mails[keys[0]].content).to.deep.eq(getTestMail().content); }); }); }); diff --git a/src/contracts/signer-identity.ts b/src/contracts/signer-identity.ts index 3ee16023..dc2337e7 100644 --- a/src/contracts/signer-identity.ts +++ b/src/contracts/signer-identity.ts @@ -55,17 +55,28 @@ export interface SignerIdentityOptions extends LoggerOptions { */ export class SignerIdentity extends Logger implements SignerInterface { public activeIdentity: string; + public underlyingAccount: string; + private coder: any = new AbiCoder(); + private config: SignerIdentityConfig; + private options: SignerIdentityOptions; - public constructor(options: SignerIdentityOptions, config: SignerIdentityConfig) { + /** + * Creates a new `SignerInternal` instance. `config` can be set up later on with `updateConfig`, + * if required (e.g. when initializing a circular structure). + * + * @param {SignerIdentityOptions} options runtime like object + * @param {SignerIdentityConfig} config (optional) config for `SignerInternal` + */ + public constructor(options: SignerIdentityOptions, config: SignerIdentityConfig = null) { super(options); this.options = options; - this.config = config; - this.activeIdentity = this.config.activeIdentity; - this.underlyingAccount = this.config.underlyingAccount; + if (config) { + this.updateConfig(options, config); + } } /** @@ -91,20 +102,42 @@ export class SignerIdentity extends Logger implements SignerInterface { throw new Error(`cannot find contract bytecode for contract "${contractName}"`); } // build bytecode and arguments for constructor - options.input = `0x${compiledContract.bytecode}` + - this.encodeConstructorParams(JSON.parse(compiledContract.interface), functionArguments); + // eslint-disable-next-line no-param-reassign + options.input = `0x${compiledContract.bytecode}${ + this.encodeConstructorParams(JSON.parse(compiledContract.interface), functionArguments)}`; - const { blockNumber, transactionHash } = await this.handleIdentityTransaction(null, null, [], options); + const { blockNumber, transactionHash } = await this.handleIdentityTransaction( + null, + null, + [], + options, + ); const keyHolderLibrary = this.options.contractLoader.loadContract( - 'KeyHolderLibrary', this.activeIdentity); + 'KeyHolderLibrary', this.activeIdentity, + ); const events = await keyHolderLibrary.getPastEvents( - 'ContractCreated', { fromBlock: blockNumber, toBlock: blockNumber }); - const matches = events.filter(ev => ev.transactionHash === transactionHash); + 'ContractCreated', { fromBlock: blockNumber, toBlock: blockNumber }, + ); + const matches = events.filter((ev) => ev.transactionHash === transactionHash); if (matches.length !== 1) { throw new Error('contract creation failed'); } return this.options.contractLoader.loadContract( - contractName, matches[0].returnValues.contractId); + contractName, matches[0].returnValues.contractId, + ); + } + + /** + * get public key for given account + * + * @param {string} accountId account to get public key for + */ + public async getPublicKey(accountId: string): Promise { + if (accountId !== this.underlyingAccount) { + throw new Error('getting public key is only supported for \'underlyingAccount\''); + } + + return this.config.underlyingSigner.getPublicKey(accountId); } /** @@ -115,6 +148,7 @@ export class SignerIdentity extends Logger implements SignerInterface { * @param {Function} handleTxResult result handler function * @return {Promise} resolved when done */ + // eslint-disable-next-line consistent-return public async signAndExecuteSend( options: any, handleTxResult: Function, @@ -145,6 +179,7 @@ export class SignerIdentity extends Logger implements SignerInterface { * @param {Function} handleTxResult callback(error, result) * @return {Promise} resolved when done */ + // eslint-disable-next-line consistent-return public async signAndExecuteTransaction( contract: any, functionName: string, @@ -154,7 +189,8 @@ export class SignerIdentity extends Logger implements SignerInterface { ): Promise { if (options.from === this.underlyingAccount) { return this.config.underlyingSigner.signAndExecuteTransaction( - contract, functionName, functionArguments, options, handleTxResult); + contract, functionName, functionArguments, options, handleTxResult, + ); } try { @@ -178,11 +214,28 @@ export class SignerIdentity extends Logger implements SignerInterface { accountId: string, message: string, ): Promise { - if (accountId === this.underlyingAccount) { - return this.config.underlyingSigner.signMessage(accountId, message); - } else { - throw new Error('signing messages with identities is not supported'); + if (accountId !== this.underlyingAccount) { + throw new Error('signing messages with identities is only supported for \'underlyingAccount\''); } + return this.config.underlyingSigner.signMessage(accountId, message); + } + + /** + * Update config of `SignerInternal` can also be used to setup verifications and accounts after + * initial setup and linking with other modules. + * + * @param {{ verifications: Verifications }} partialOptions object with `verifications` + * property, e.g. a runtime + * @param {SignerIdentityConfig} config signer identity config + */ + public updateConfig( + partialOptions: { verifications: Verifications }, + config: SignerIdentityConfig, + ): void { + this.options.verifications = partialOptions.verifications; + this.config = config; + this.activeIdentity = this.config.activeIdentity; + this.underlyingAccount = this.config.underlyingAccount; } /** @@ -197,14 +250,12 @@ export class SignerIdentity extends Logger implements SignerInterface { private encodeConstructorParams(abi: any[], params: any[]) { if (params.length) { return abi - .filter(json => json.type === 'constructor' && json.inputs.length === params.length) - .map(json => json.inputs.map(input => input.type)) - .map(types => this.coder.encodeParameters(types, params)) - .map(encodedParams => encodedParams.replace(/^0x/, ''))[0] || '' - ; - } else { - return ''; + .filter((json) => json.type === 'constructor' && json.inputs.length === params.length) + .map((json) => json.inputs.map((input) => input.type)) + .map((types) => this.coder.encodeParameters(types, params)) + .map((encodedParams) => encodedParams.replace(/^0x/, ''))[0] || ''; } + return ''; } /** @@ -231,7 +282,7 @@ export class SignerIdentity extends Logger implements SignerInterface { contract, functionName, optionsClone, - ...functionArguments + ...functionArguments, ); const txResult = await this.options.verifications.executeTransaction( this.underlyingAccount, @@ -239,12 +290,14 @@ export class SignerIdentity extends Logger implements SignerInterface { { event: { contract: this.options.contractLoader.loadContract( - 'VerificationHolder', this.activeIdentity), + 'VerificationHolder', this.activeIdentity, + ), eventName: 'ExecutionRequested', }, - getEventResult: ({ transactionHash }) => - this.options.web3.eth.getTransactionReceipt(transactionHash), - } + getEventResult: ({ transactionHash }) => this.options.web3.eth.getTransactionReceipt( + transactionHash, + ), + }, ); return txResult; } diff --git a/src/contracts/wallet.spec.ts b/src/contracts/wallet.spec.ts index b9ffa99b..783ee43d 100644 --- a/src/contracts/wallet.spec.ts +++ b/src/contracts/wallet.spec.ts @@ -19,30 +19,23 @@ import 'mocha'; import { expect, use } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); +import * as chaiAsPromised from 'chai-as-promised'; import { - ContractLoader, DfsInterface, Executor, - NameResolver, } from '@evan.network/dbcp'; import { accounts } from '../test/accounts'; -import { configTestcore as config } from '../config-testcore'; -import { CryptoProvider } from '../encryption/crypto-provider'; -import { Ipfs } from '../dfs/ipfs'; -import { sampleContext, TestUtils } from '../test/test-utils'; -import { Sharing } from './sharing'; +import { TestUtils } from '../test/test-utils'; import { Wallet } from './wallet'; use(chaiAsPromised); -describe('Wallet handler', function() { +describe('Wallet handler', function test() { this.timeout(60000); let dfs: DfsInterface; - let contractLoader: ContractLoader; let executor: Executor; let wallet: Wallet; let web3: any; @@ -52,7 +45,6 @@ describe('Wallet handler', function() { dfs = await TestUtils.getIpfs(); wallet = await TestUtils.getWallet(web3, dfs); executor = await TestUtils.getExecutor(web3); - contractLoader = await TestUtils.getContractLoader(web3); }); function runTests(walletType, createWallet) { @@ -64,7 +56,7 @@ describe('Wallet handler', function() { // create wallet by hand const walletContract = await executor.createContract( walletType, - [ [ accounts[0] ], 1 ], + [[accounts[0]], 1], { from: accounts[0], gas: 2000000 }, ); wallet.load(walletContract.options.address, walletType); @@ -78,17 +70,17 @@ describe('Wallet handler', function() { await createWallet(accounts[0], accounts[0], [accounts[0], accounts[1]]); // create test contract and hand over to wallet - const testContract = await executor.createContract('Owned', [], { from: accounts[0], gas: 200000, }); + const testContract = await executor.createContract('Owned', [], { from: accounts[0], gas: 200000 }); expect(await executor.executeContractCall(testContract, 'owner')).to.eq(accounts[0]); - await executor.executeContractTransaction(testContract, 'transferOwnership', { from: accounts[0], }, wallet.walletAddress); + await executor.executeContractTransaction(testContract, 'transferOwnership', { from: accounts[0] }, wallet.walletAddress); expect(await executor.executeContractCall(testContract, 'owner')).to.eq(wallet.walletAddress); - await wallet.submitTransaction(testContract, 'transferOwnership', { from: accounts[0], }, accounts[1]); + await wallet.submitTransaction(testContract, 'transferOwnership', { from: accounts[0] }, accounts[1]); expect(await executor.executeContractCall(testContract, 'owner')).to.eq(accounts[1]); - await executor.executeContractTransaction(testContract, 'transferOwnership', { from: accounts[1], }, wallet.walletAddress); + await executor.executeContractTransaction(testContract, 'transferOwnership', { from: accounts[1] }, wallet.walletAddress); - await wallet.submitTransaction(testContract, 'transferOwnership', { from: accounts[1], }, accounts[0]); + await wallet.submitTransaction(testContract, 'transferOwnership', { from: accounts[1] }, accounts[0]); expect(await executor.executeContractCall(testContract, 'owner')).to.eq(accounts[0]); }); @@ -96,12 +88,12 @@ describe('Wallet handler', function() { await createWallet(accounts[0], accounts[0], [accounts[0]]); // create test contract and hand over to wallet - const testContract = await executor.createContract('Owned', [], { from: accounts[0], gas: 200000, }); + const testContract = await executor.createContract('Owned', [], { from: accounts[0], gas: 200000 }); expect(await executor.executeContractCall(testContract, 'owner')).to.eq(accounts[0]); - await executor.executeContractTransaction(testContract, 'transferOwnership', { from: accounts[0], }, wallet.walletAddress); + await executor.executeContractTransaction(testContract, 'transferOwnership', { from: accounts[0] }, wallet.walletAddress); expect(await executor.executeContractCall(testContract, 'owner')).to.eq(wallet.walletAddress); - const promise = wallet.submitTransaction(testContract, 'transferOwnership', { from: accounts[1], }, accounts[1]); + const promise = wallet.submitTransaction(testContract, 'transferOwnership', { from: accounts[1] }, accounts[1]); await expect(promise).to.be.rejected; }); }); @@ -109,11 +101,12 @@ describe('Wallet handler', function() { describe('when submitting transactions on wallets for multiple accounts', () => { async function createContract() { const contract = await executor.createContract( - 'Owned', [], { from: accounts[0], gas: 200000, }); + 'Owned', [], { from: accounts[0], gas: 200000 }, + ); await executor.executeContractTransaction( contract, 'transferOwnership', - { from: accounts[0], }, + { from: accounts[0] }, wallet.walletAddress, ); return contract; @@ -128,16 +121,19 @@ describe('Wallet handler', function() { const testContract = await createContract(); // test with account1 await expect(wallet.submitTransaction( - testContract, 'transferOwnership', { from: accounts[0], }, accounts[1])).not.to.be.rejected; + testContract, 'transferOwnership', { from: accounts[0] }, accounts[1], + )).not.to.be.rejected; // test with account2 await expect(wallet.submitTransaction( - testContract, 'transferOwnership', { from: accounts[1], }, accounts[0])).not.to.be.rejected; + testContract, 'transferOwnership', { from: accounts[1] }, accounts[0], + )).not.to.be.rejected; }); it('returns txinfo upon submitting a tx and missing confirmations', async () => { const testContract = await createContract(); const txInfo = await wallet.submitTransaction( - testContract, 'transferOwnership', { from: accounts[0], }, accounts[1]); + testContract, 'transferOwnership', { from: accounts[0] }, accounts[1], + ); expect(txInfo).to.be.ok; expect(txInfo.result).to.be.ok; expect(txInfo.result.status).to.eq('pending'); @@ -151,7 +147,8 @@ describe('Wallet handler', function() { it('executes tx when submitting the final confirmation', async () => { const testContract = await createContract(); const txInfo = await wallet.submitTransaction( - testContract, 'transferOwnership', { from: accounts[0], }, accounts[1]); + testContract, 'transferOwnership', { from: accounts[0] }, accounts[1], + ); // still owned by wallet expect(await executor.executeContractCall(testContract, 'owner')) @@ -165,63 +162,56 @@ describe('Wallet handler', function() { }); describe('when submitting funds alongside transactions', () => { - async function createContract(accountId) { - const contract = await executor.createContract( - 'TestContract', [], { from: accounts[0], gas: 1000000, }); - await executor.executeContractTransaction( - contract, - 'transferOwnership', - { from: accounts[0], }, - wallet.walletAddress, - ); - return contract; - } - it('allows to transfer funds to wallet', async () => { await createWallet(accounts[0], accounts[0], [accounts[0]]); - const walletAddress = wallet.walletAddress; + const { walletAddress } = wallet; const valueToSend = Math.floor(Math.random() * 10000); const executeSendP = executor.executeSend( - { from: accounts[0], to: walletAddress, value: valueToSend }); + { from: accounts[0], to: walletAddress, value: valueToSend }, + ); await expect(executeSendP).not.to.be.rejected; expect(await web3.eth.getBalance(walletAddress)).to.eq(valueToSend.toString()); }); it('instantly submits funds to target if instantly submitting transaction', async () => { await createWallet(accounts[0], accounts[0], [accounts[0]]); - const walletAddress = wallet.walletAddress; + const { walletAddress } = wallet; const valueToSend = Math.floor(Math.random() * 10000); await executor.executeSend({ from: accounts[0], to: walletAddress, value: valueToSend }); expect(await web3.eth.getBalance(walletAddress)).to.eq(valueToSend.toString()); const testContract = await executor.createContract( - 'TestContract', ['test'], { from: accounts[0], gas: 1000000, }); + 'TestContract', ['test'], { from: accounts[0], gas: 1000000 }, + ); expect(await web3.eth.getBalance(testContract.options.address)).to.eq('0'); - const txInfo = await wallet.submitTransaction(testContract, 'chargeFunds', { from: accounts[0], value: valueToSend, }); - expect(await web3.eth.getBalance(testContract.options.address)).to.eq(valueToSend.toString()); + await wallet.submitTransaction(testContract, 'chargeFunds', { from: accounts[0], value: valueToSend }); + expect(await web3.eth.getBalance(testContract.options.address)) + .to.eq(valueToSend.toString()); expect(await web3.eth.getBalance(walletAddress)).to.eq('0'); }); it('waits for final confirmation to transfer funds, when multisigning transactions', async () => { await createWallet(accounts[0], accounts[0], [accounts[0], accounts[1]], 2); - const walletAddress = wallet.walletAddress; + const { walletAddress } = wallet; const valueToSend = Math.floor(Math.random() * 10000); await executor.executeSend({ from: accounts[0], to: walletAddress, value: valueToSend }); expect(await web3.eth.getBalance(walletAddress)).to.eq(valueToSend.toString()); const testContract = await executor.createContract( - 'TestContract', ['test'], { from: accounts[0], gas: 1000000, }); + 'TestContract', ['test'], { from: accounts[0], gas: 1000000 }, + ); expect(await web3.eth.getBalance(testContract.options.address)).to.eq('0'); - const txInfo = await wallet.submitTransaction(testContract, 'chargeFunds', { from: accounts[0], value: valueToSend, }); + const txInfo = await wallet.submitTransaction(testContract, 'chargeFunds', { from: accounts[0], value: valueToSend }); expect(await web3.eth.getBalance(testContract.options.address)).to.eq('0'); expect(await web3.eth.getBalance(walletAddress)).to.eq(valueToSend.toString()); await wallet.confirmTransaction(accounts[1], txInfo.result.transactionId); - expect(await web3.eth.getBalance(testContract.options.address)).to.eq(valueToSend.toString()); + expect(await web3.eth.getBalance(testContract.options.address)) + .to.eq(valueToSend.toString()); expect(await web3.eth.getBalance(walletAddress)).to.eq('0'); }); }); @@ -229,7 +219,11 @@ describe('Wallet handler', function() { describe('when using managed wallets', () => { async function createWallet( - executingAccount: string, manager: string, participants: string[], confirmations?: number) { + executingAccount: string, + manager: string, + participants: string[], + confirmations?: number, + ) { // create wallet via factory, returned wallet is of type 'MultiSigWallet' if (typeof confirmations !== 'undefined') { await wallet.create(executingAccount, manager, participants, confirmations); @@ -271,11 +265,15 @@ describe('Wallet handler', function() { describe('when using self governed wallets', () => { async function createWallet( - executingAccount: string, manager: string, participants: string[], confirmations?: number) { + executingAccount: string, + manager: string, + participants: string[], + confirmations?: number, + ) { // create wallet by hand const walletContract = await executor.createContract( 'MultiSigWalletSG', - [ participants, typeof confirmations !== 'undefined' ? confirmations : 1 ], + [participants, typeof confirmations !== 'undefined' ? confirmations : 1], { from: executingAccount, gas: 2000000 }, ); wallet.load(walletContract.options.address, 'MultiSigWalletSG'); diff --git a/src/contracts/wallet.ts b/src/contracts/wallet.ts index f7ae2680..4e6f4e97 100644 --- a/src/contracts/wallet.ts +++ b/src/contracts/wallet.ts @@ -21,7 +21,6 @@ import { AbiCoder } from 'web3-eth-abi'; import { ContractLoader, - Description, EventHub, Executor, Logger, @@ -29,6 +28,8 @@ import { NameResolver, } from '@evan.network/dbcp'; +import { Description } from '../index'; + const coder: AbiCoder = new AbiCoder(); @@ -47,50 +48,55 @@ export interface WalletOptions extends LoggerOptions { * @class Sharing (name) */ export class Wallet extends Logger { - options: WalletOptions; - defaultOptions: any; - defaultDescription: any = { - 'public': { - 'name': 'MultiSigWallet contract', - 'description': 'allows multiple accounts to agree on transactions', - 'author': 'evan.network GmbH', - 'version': '0.0.1', - 'dbcpVersion': 1, - } + public options: WalletOptions; + + public defaultOptions: any; + + public defaultDescription: any = { + public: { + name: 'MultiSigWallet contract', + description: 'allows multiple accounts to agree on transactions', + author: 'evan.network GmbH', + version: '0.0.1', + dbcpVersion: 1, + }, }; - receipts = {}; - walletType: string; - walletContract: any; - get walletAddress() { + public receipts = {}; + + public walletType: string; + + public walletContract: any; + + public get walletAddress() { return this.walletContract ? this.walletContract.options.address : null; } - constructor(options: WalletOptions) { + public constructor(options: WalletOptions) { super(options); this.options = options; this.defaultOptions = options.defaultOptions || {}; this.defaultDescription.public.abis = { own: JSON.parse(this.options.contractLoader.contracts.MultiSigWallet.interface), }; - const that = this; - const signAndExecuteTransaction = this.options.executor.signer.signAndExecuteTransaction; - this.options.executor.signer.signAndExecuteTransaction = - function(contract, functionName, functionArguments, innerOptions, handleTxResult) { - signAndExecuteTransaction.call( - that.options.executor.signer, - contract, - functionName, - functionArguments, - innerOptions, - (error, receipt) => { - if (receipt) { - that.receipts[receipt.transactionHash] = receipt; - } - handleTxResult(error, receipt); - }, - ); - }; + const { signAndExecuteTransaction } = this.options.executor.signer; + this.options.executor.signer.signAndExecuteTransaction = ( + contract, functionName, functionArguments, innerOptions, handleTxResult, + ) => { + signAndExecuteTransaction.call( + this.options.executor.signer, + contract, + functionName, + functionArguments, + innerOptions, + (error, receipt) => { + if (receipt) { + this.receipts[receipt.transactionHash] = receipt; + } + handleTxResult(error, receipt); + }, + ); + }; } /** @@ -105,8 +111,8 @@ export class Wallet extends Logger { await this.options.executor.executeContractTransaction( this.ensureContract(), 'addOwner', - { from: accountId, }, - toAdd + { from: accountId }, + toAdd, ); } else if (this.walletType === 'MultiSigWalletSG') { await this.submitTransaction( @@ -122,7 +128,8 @@ export class Wallet extends Logger { public async confirmTransaction(accountId: string, transactionId: string|number): Promise { return this.submitAndHandleConfirmation( - null, 'confirmTransaction', { from: accountId }, transactionId); + null, 'confirmTransaction', { from: accountId }, transactionId, + ); } /** @@ -135,8 +142,12 @@ export class Wallet extends Logger { * transaction, defaults to 1 * @return {Promise} resolved when done */ - public async create(accountId: string, manager: string, owners: string[], confirmations = 1): - Promise { + public async create( + accountId: string, + manager: string, + owners: string[], + confirmations = 1, + ): Promise { // get factory const factoryDomain = [ this.options.nameResolver.config.labels.wallet, @@ -145,8 +156,8 @@ export class Wallet extends Logger { ].join('.'); const factoryAddress = await this.options.nameResolver.getAddress(factoryDomain); if (!factoryAddress) { - throw new Error(`factory '${factoryDomain}' for creating wallets not found in ` + - `'${this.options.nameResolver.config.labels.businessCenterRoot}'`); + throw new Error(`factory '${factoryDomain}' for creating wallets not found in ` + + `'${this.options.nameResolver.config.labels.businessCenterRoot}'`); } const factory = this.options.contractLoader.loadContract('MultiSigWalletFactory', factoryAddress); // create contract via factory @@ -155,7 +166,7 @@ export class Wallet extends Logger { 'createContract', { from: accountId, autoGas: 1.1, - event: { target: 'MultiSigWalletFactory', eventName: 'ContractCreated', }, + event: { target: 'MultiSigWalletFactory', eventName: 'ContractCreated' }, getEventResult: (event, args) => args.newAddress, }, manager, @@ -164,7 +175,8 @@ export class Wallet extends Logger { ); // add description await this.options.description.setDescriptionToContract( - contractId, this.defaultDescription, accountId); + contractId, this.defaultDescription, accountId, + ); this.walletType = 'MultiSigWallet'; this.walletContract = this.options.contractLoader.loadContract(this.walletType, contractId); @@ -203,8 +215,8 @@ export class Wallet extends Logger { await this.options.executor.executeContractTransaction( this.ensureContract(), 'removeOwner', - { from: accountId, }, - toRemove + { from: accountId }, + toRemove, ); } else if (this.walletType === 'MultiSigWalletSG') { await this.submitTransaction( @@ -229,10 +241,10 @@ export class Wallet extends Logger { * transaction * @return {Promise} status information about transaction */ - public async submitRawTransaction( - target: any, encoded: string, inputOptions: any): Promise { + public async submitRawTransaction(target: any, encoded: string, inputOptions: any): Promise { return this.submitAndHandleConfirmation( - target, 'submitTransaction', inputOptions, encoded); + target, 'submitTransaction', inputOptions, encoded, + ); } /** @@ -247,7 +259,8 @@ export class Wallet extends Logger { * @return {Promise} status information about transaction */ public async submitTransaction( - target: any, functionName: string, inputOptions: any, ...functionArguments): Promise { + target: any, functionName: string, inputOptions: any, ...functionArguments + ): Promise { // serialize data const encoded = this.encodeFunctionParams(functionName, target, functionArguments); return this.submitRawTransaction(target, encoded, inputOptions); @@ -258,32 +271,27 @@ export class Wallet extends Logger { if (contractInstance.abiModel) { functionAbi = contractInstance.abiModel.getMethod(functionName).abiItem; } else { - functionAbi = contractInstance.options.jsonInterface - .filter(json => json.name === functionName && json.inputs.length === params.length)[0] + [functionAbi] = contractInstance.options.jsonInterface + .filter((json) => json.name === functionName && json.inputs.length === params.length); } - const types = functionAbi.inputs.map(input => input.type); + const types = functionAbi.inputs.map((input) => input.type); const signature = this.options.executor.web3.eth.abi.encodeFunctionSignature(functionAbi); return `${signature}${coder.encodeParameters(types, params).replace('0x', '')}`; } - private ensureContract(): any { - if (!this.walletContract) { - throw new Error('no wallet contract specified at wallet helper, load or create one'); - } - return this.walletContract; - } - public async submitAndHandleConfirmation( - target: any, functionName: string, inputOptions: any, ...functionArguments): Promise { + target: any, functionName: string, inputOptions: any, ...functionArguments + ): Promise { const subscriptions = []; let receipt; - let walletOptions = Object.assign( - { timeout: 300000 }, - this.defaultOptions || {}, - inputOptions, - ); + const walletOptions = { + timeout: 300000, + ...this.defaultOptions || {}, + ...inputOptions, + }; try { + // eslint-disable-next-line no-async-promise-executor receipt = await new Promise(async (resolve, reject) => { try { let txResolved; @@ -291,7 +299,7 @@ export class Wallet extends Logger { setTimeout(() => { if (!txResolved) { txResolved = true; - reject(`wallet timeout after ${transactionTimeout}ms`); + reject(new Error(`wallet timeout after ${transactionTimeout}ms`)); } }, transactionTimeout); @@ -301,14 +309,16 @@ export class Wallet extends Logger { const transactionResults = {}; // helper functions + // eslint-disable-next-line consistent-return const resolveIfPossible = () => { - if (transactionHash && // from Confirmation event - walletTransactionId && // from Confirmation event - this.receipts[transactionHash] && // from signer receipt fetching - transactionResults[walletTransactionId].event) { // from Execution/ExecutionFailure + if (transactionHash // from Confirmation event + && walletTransactionId // from Confirmation event + && this.receipts[transactionHash] // from signer receipt fetching + // from Execution/ExecutionFailure + && transactionResults[walletTransactionId].event) { txResolved = true; if (transactionResults[walletTransactionId].event.event === 'ExecutionFailure') { - return reject('ExecutionFailure'); + return reject(new Error('ExecutionFailure')); } resolve(transactionResults[walletTransactionId]); } @@ -316,43 +326,43 @@ export class Wallet extends Logger { // subscribe to events for status tracking const walletInstance = this.ensureContract(); - const handleConfirmation = async(event) => { + const handleConfirmation = async (event) => { try { // get all events from block const events = {}; const eventNames = []; let eventList = await walletInstance.getPastEvents( 'allEvents', - { fromBlock: event.blockNumber, toBlock: event.blockNumber }); - eventList = eventList.filter(ev => ev.transactionHash === event.transactionHash); + { fromBlock: event.blockNumber, toBlock: event.blockNumber }, + ); + eventList = eventList.filter((ev) => ev.transactionHash === event.transactionHash); eventList.forEach((entry) => { events[entry.event] = entry; eventNames.push(entry.event); }); transactionResults[event.returnValues.transactionId] = { event }; if (eventNames.includes('ContractCreated')) { - this.log('received MultiSigWallet ContractCreated event with txid: ' + - `${event.returnValues.transactionId} ${event.transactionHash}`, 'debug'); - transactionResults[event.returnValues.transactionId].result = - eventList.filter(ev => ev.event === 'ContractCreated')[0].returnValues.contractId; + this.log('received MultiSigWallet ContractCreated event with txid: ' + + `${event.returnValues.transactionId} ${event.transactionHash}`, 'debug'); + transactionResults[event.returnValues.transactionId].result = eventList.filter((ev) => ev.event === 'ContractCreated')[0].returnValues.contractId; } else if (eventNames.includes('Execution')) { - this.log('received MultiSigWallet Execution event with txid: ' + - `${event.returnValues.transactionId} ${event.transactionHash}`, 'debug'); + this.log('received MultiSigWallet Execution event with txid: ' + + `${event.returnValues.transactionId} ${event.transactionHash}`, 'debug'); } else if (eventNames.includes('ExecutionFailure')) { - this.log('received MultiSigWallet ExecutionFailure event with txid: ' + - `${event.returnValues.transactionId} ${event.transactionHash}`, 'debug'); + this.log('received MultiSigWallet ExecutionFailure event with txid: ' + + `${event.returnValues.transactionId} ${event.transactionHash}`, 'debug'); } else { - this.log('received MultiSigWallet Confirmation event with hash: ' + - `${event.transactionHash} and txid: ${event.returnValues.transactionId}`, 'debug'); + this.log('received MultiSigWallet Confirmation event with hash: ' + + `${event.transactionHash} and txid: ${event.returnValues.transactionId}`, 'debug'); transactionResults[event.returnValues.transactionId].result = { status: 'pending', transactionId: event.returnValues.transactionId, }; } } catch (ex) { - const msg = 'handling of confirmation of ' + - (target ? `transaction to "${target.options.address}"` : 'constructor') + - ` with ${ex.message || ex}`; + const msg = `handling of confirmation of ${ + target ? `transaction to "${target.options.address}"` : 'constructor' + } with ${ex.message || ex}`; this.log(msg, 'error'); throw new Error(msg); } @@ -360,20 +370,20 @@ export class Wallet extends Logger { // execute to contract const options = Object.assign(JSON.parse(JSON.stringify(walletOptions)), { - event: { target: 'MultiSigWallet', eventName: 'Confirmation', }, + event: { target: 'MultiSigWallet', eventName: 'Confirmation' }, getEventResult: async (event, args) => { - this.log('received MultiSigWallet Confirmation event with hash: ' + - `${event.transactionHash} and txid: ${args.transactionId}`, 'debug'); + this.log('received MultiSigWallet Confirmation event with hash: ' + + `${event.transactionHash} and txid: ${args.transactionId}`, 'debug'); transactionHash = event.transactionHash; walletTransactionId = args.transactionId; await handleConfirmation(event); resolveIfPossible(); - } + }, }); let value = 0; if (options.value) { - this.log('wallet transaction has "value" set, removing this from tx options ' + - 'and passing it as argument', 'debug'); + this.log('wallet transaction has "value" set, removing this from tx options ' + + 'and passing it as argument', 'debug'); value = options.value; delete options.value; } @@ -382,8 +392,8 @@ export class Wallet extends Logger { walletInstance, functionName, options, - (target && target.options) ? - target.options.address : '0x0000000000000000000000000000000000000000', + (target && target.options) + ? target.options.address : '0x0000000000000000000000000000000000000000', value, ...functionArguments, ); @@ -404,9 +414,17 @@ export class Wallet extends Logger { } finally { // cleanup subscriptions await Promise.all( - subscriptions.map(s => this.options.eventHub.unsubscribe({ subscription: s }))); + subscriptions.map((s) => this.options.eventHub.unsubscribe({ subscription: s })), + ); } return receipt; } + + private ensureContract(): any { + if (!this.walletContract) { + throw new Error('no wallet contract specified at wallet helper, load or create one'); + } + return this.walletContract; + } } diff --git a/src/dfs/in-memory-cache.ts b/src/dfs/in-memory-cache.ts index 5aed61f2..329472b6 100644 --- a/src/dfs/in-memory-cache.ts +++ b/src/dfs/in-memory-cache.ts @@ -18,19 +18,21 @@ */ import { - DfsCacheInterface + DfsCacheInterface, } from '@evan.network/dbcp'; + /** * in-memory cache for DFS requests, * that allows to keep retrieved items once and skip external requests in DFS * * @class InMemoryCache (name) */ +// eslint-disable-next-line import/prefer-default-export export class InMemoryCache implements DfsCacheInterface { private cache; - constructor() { + public constructor() { this.cache = {}; } @@ -42,7 +44,7 @@ export class InMemoryCache implements DfsCacheInterface { * @return {Promise} reference to the file in the DFS, format may differ depending on * the type of DFS */ - async add(hash: string, data: any): Promise { + public async add(hash: string, data: any): Promise { this.cache[hash] = data; } @@ -53,7 +55,7 @@ export class InMemoryCache implements DfsCacheInterface { * depending on the type of DFS * @return {Promise} file content as buffer */ - async get(hash: string): Promise { + public async get(hash: string): Promise { return this.cache[hash]; } } diff --git a/src/dfs/ipfs-lib.ts b/src/dfs/ipfs-lib.ts index e97a13b3..0bc9e06e 100644 --- a/src/dfs/ipfs-lib.ts +++ b/src/dfs/ipfs-lib.ts @@ -22,28 +22,30 @@ import * as https from 'https'; import { FileToAdd, -} from '@evan.network/dbcp' +} from '@evan.network/dbcp'; /** * @brief IPFS library for add/pin */ +// eslint-disable-next-line import/prefer-default-export export class IpfsLib { - /** * compatible IPFS files api */ - files: any; + public files: any; + /** * compatible IPFS pin api */ - pin: any; + public pin: any; + /** * holds the provider */ - provider: any; + public provider: any; - constructor(provider: any) { + public constructor(provider: any) { this.setProvider(provider || {}); } @@ -52,25 +54,26 @@ export class IpfsLib { * * @param {any} provider The provider */ - setProvider(provider: any): void { - const data = Object.assign({ + public setProvider(provider: any): void { + const data = { host: '127.0.0.1', pinning: true, port: '5001', protocol: 'http', base: '/api/v0', headers: {}, - }, provider || {}); + ...provider || {}, + }; this.provider = data; this.files = { add: this.add.bind(this), - cat: this.cat.bind(this) + cat: this.cat.bind(this), }; this.pin = { add: this.pinAdd.bind(this), - rm: this.pinRm.bind(this) + rm: this.pinRm.bind(this), }; - }; + } /** @@ -78,14 +81,13 @@ export class IpfsLib { * * @param {object} opts The options for the request */ - async sendAsync(opts) { - + public async sendAsync(opts) { return new Promise((resolve, reject) => { const requestLib: any = this.provider.protocol === 'http' ? http : https; const reqOptions: http.RequestOptions = {}; reqOptions.hostname = this.provider.host; reqOptions.path = `${this.provider.base}${opts.uri}`; - reqOptions.headers = Object.assign({}, this.provider.headers); + reqOptions.headers = { ...this.provider.headers }; if (opts.payload) { reqOptions.method = 'POST'; reqOptions.headers['Content-Type'] = `multipart/form-data; boundary=${opts.boundary}`; @@ -94,26 +96,27 @@ export class IpfsLib { } if (opts.accept) { - reqOptions.headers['accept'] = opts.accept; + reqOptions.headers.accept = opts.accept; } - const req: http.ClientRequest = requestLib.request(reqOptions, (res: http.IncomingMessage) => { - const data = []; - res.on('data', (chunk) => { - data.push(chunk); - }); - res.on('end', () => { - const binary = Buffer.concat(data); - if (res.statusCode >= 200 && res.statusCode < 400) { - try { - resolve((opts.jsonParse ? JSON.parse(binary.toString()) : binary)); - } catch (jsonError) { - reject(new Error(`error while parsing ipfs binary data: '${String(binary)}', error: ${String(jsonError)}'`)); + const req: http.ClientRequest = requestLib.request(reqOptions, + (res: http.IncomingMessage) => { + const data = []; + res.on('data', (chunk) => { + data.push(chunk); + }); + res.on('end', () => { + const binary = Buffer.concat(data); + if (res.statusCode >= 200 && res.statusCode < 400) { + try { + resolve((opts.jsonParse ? JSON.parse(binary.toString()) : binary)); + } catch (jsonError) { + reject(new Error(`error while parsing ipfs binary data: '${String(binary)}', error: ${String(jsonError)}'`)); + } + } else { + reject(new Error(`problem with IPFS request: ${String(binary)}'`)); } - } else { - reject(new Error(`problem with IPFS request: ${String(binary)}'`)); - } + }); }); - }); req.on('error', (e) => { reject(new Error(`problem with request: ${e.message}`)); @@ -125,37 +128,36 @@ export class IpfsLib { } req.end(); }); - }; + } /** * adds files to ipfs * * @param {FileToAdd} input Array with to be pushed files */ - async add(input: FileToAdd[]) { + public async add(input: FileToAdd[]) { let files = input; if (!Array.isArray(files)) { - files = [].concat(files) + files = [].concat(files); } const response = []; - for (let file of files) { + for (const file of files) { const boundary = this.createBoundary(); const header = [ `--${boundary}`, `Content-Disposition: form-data; name="${file.path}"`, 'Content-Type: application/octet-stream', '', - '' + '', ].join('\r\n'); - const data = file.content; const footer = `\r\n--${boundary}--`; const payload = Buffer.concat([ Buffer.from(header), file.content, - Buffer.from(footer) - ]) + Buffer.from(footer), + ]); response.push(await this.sendAsync({ jsonParse: true, @@ -167,13 +169,12 @@ export class IpfsLib { })); } return response; - - }; + } /** * creates a boundary that isn't part of the payload */ - createBoundary() { + public createBoundary() { const boundary = `----EVANipfs${Math.random() * 100000}.${Math.random() * 100000}`; return boundary; } @@ -183,25 +184,25 @@ export class IpfsLib { * * @param {string} ipfsHash The ipfs hash */ - async cat(ipfsHash: string) { + public async cat(ipfsHash: string) { return this.sendAsync({ uri: `/cat?arg=${ipfsHash}` }); - }; + } /** * adds a pin to IPFS * * @param {string} ipfsHash The ipfs hash */ - async pinAdd(ipfsHash: string) { + public async pinAdd(ipfsHash: string) { return this.sendAsync({ uri: `/pin/add?arg=${ipfsHash}`, jsonParse: true }); - }; + } /** * removes a pin from IPFS * * @param {string} ipfsHash The ipfs hash */ - async pinRm(ipfsHash: string) { + public async pinRm(ipfsHash: string) { return this.sendAsync({ uri: `/pin/rm?arg=${ipfsHash}`, jsonParse: true }); - }; + } } diff --git a/src/dfs/ipfs.spec.ts b/src/dfs/ipfs.spec.ts index ea54be8a..bfd613c4 100644 --- a/src/dfs/ipfs.spec.ts +++ b/src/dfs/ipfs.spec.ts @@ -19,19 +19,18 @@ import 'mocha'; import * as chaiAsPromised from 'chai-as-promised'; -import IpfsApi = require('ipfs-api'); import { expect, use } from 'chai'; -import { Ipfs } from './ipfs' -import { InMemoryCache } from './in-memory-cache' -import { TestUtils } from '../test/test-utils' +import { Ipfs } from './ipfs'; +import { InMemoryCache } from './in-memory-cache'; +import { TestUtils } from '../test/test-utils'; use(chaiAsPromised); let ipfs: Ipfs; -describe('IPFS handler', function() { +describe('IPFS handler', function test() { this.timeout(300000); before(async () => { @@ -56,23 +55,23 @@ describe('IPFS handler', function() { const randomContent = Math.random().toString(); const fileHash = await ipfs.add('test', Buffer.from(randomContent, 'utf-8')); expect(fileHash).not.to.be.undefined; - await ipfs.pinFileHash({hash: fileHash}); + await ipfs.pinFileHash({ hash: fileHash }); }); it('should be able to unpin a file', async () => { const randomContent = Math.random().toString(); const fileHash = await ipfs.add('test', Buffer.from(randomContent, 'utf-8')); expect(fileHash).not.to.be.undefined; - await ipfs.pinFileHash({hash: fileHash}); + await ipfs.pinFileHash({ hash: fileHash }); await ipfs.remove(fileHash); }); it('should throw an error when unpinning unknown hash', async () => { const unkownHash = 'QmZYJJTAV8JgVoMggSuQSSdGU4PrZSvuuXckvqpnHfpR75'; const unpinUnkown = ipfs.remove(unkownHash); - await expect(unpinUnkown).to.be.rejectedWith(`problem with IPFS request: tried to remove hash ` + - `"${ unkownHash }" for account "${ ipfs.runtime.activeAccount }", but no matching ` + - `entries found in redis`); + await expect(unpinUnkown).to.be.rejectedWith('problem with IPFS request: tried to remove hash ' + + `"${unkownHash}" for account "${ipfs.runtime.activeAccount}", but no matching ` + + 'entries found in redis'); }); it('should be able to add a file with special characters', async () => { @@ -90,14 +89,14 @@ describe('IPFS handler', function() { Math.random().toString(), Math.random().toString(), ]; - const hashes = await ipfs.addMultiple(randomContents.map(content => ( - { path: content, content: Buffer.from(content, 'utf-8')} - ))); + const hashes = await ipfs.addMultiple(randomContents.map((content) => ( + { path: content, content: Buffer.from(content, 'utf-8') } + ))); expect(hashes).not.to.be.undefined; let hashesToCheck = randomContents.length; - for (let [index, hash] of hashes.entries()) { + for (const [, hash] of hashes.entries()) { expect(randomContents).to.contain(await ipfs.get(hash)); - hashesToCheck--; + hashesToCheck -= 1; } expect(hashesToCheck).to.eq(0); }); @@ -119,10 +118,4 @@ describe('IPFS handler', function() { // remove cache after test delete ipfs.cache; }); - - it('should set the cache when passed via options', async () => { - const remoteNode = IpfsApi({host: 'ipfs.test.evan.network', port: '443', protocol: 'https'}); - const customIpfs = new Ipfs({ remoteNode, cache: new InMemoryCache()}); - expect(customIpfs.cache).to.be.ok; - }) }); diff --git a/src/dfs/ipfs.ts b/src/dfs/ipfs.ts index 1358c345..79488e0c 100644 --- a/src/dfs/ipfs.ts +++ b/src/dfs/ipfs.ts @@ -18,8 +18,6 @@ */ import { setTimeout, clearTimeout } from 'timers'; -import bs58 = require('bs58'); -import prottle = require('prottle'); import { FileToAdd, @@ -29,13 +27,15 @@ import { LoggerOptions, } from '@evan.network/dbcp'; -import { Runtime } from './../runtime'; +import { Runtime } from '../runtime'; -import { Payments } from './../payments'; +import { Payments } from '../payments'; import { IpfsLib } from './ipfs-lib'; -import utils = require('./../common/utils'); +import bs58 = require('bs58'); +import prottle = require('prottle'); +import utils = require('./../common/utils'); const IPFS_TIMEOUT = 120000; @@ -61,9 +61,13 @@ export interface IpfsOptions extends LoggerOptions { */ export class Ipfs extends Logger implements DfsInterface { public remoteNode: any; + public dfsConfig: any; + public disablePin: boolean; + public cache: DfsCacheInterface; + public runtime: Runtime; /** @@ -147,25 +151,29 @@ export class Ipfs extends Logger implements DfsInterface { throw new Error('no hash was returned'); } remoteFiles = remoteFiles.map((fileHash) => { - if (!fileHash.hash) { - fileHash.hash = fileHash.Hash; + const remoteFile = fileHash; + if (!remoteFile.hash) { + remoteFile.hash = remoteFile.Hash; } - return fileHash; + return remoteFile; }); } catch (ex) { - let msg = `could not add file to ipfs: ${ex.message || ex}`; + const msg = `could not add file to ipfs: ${ex.message || ex}`; this.log(msg); throw new Error(msg); } if (this.cache) { - await Promise.all(remoteFiles.map((remoteFile, i) => { - this.cache.add(remoteFile.hash, files[i].content); - })); + await Promise.all( + remoteFiles.map((remoteFile, i) => this.cache.add(remoteFile.hash, files[i].content)), + ); } if (!this.disablePin) { - await prottle(requestWindowSize, remoteFiles.map((fileHash) => () => this.pinFileHash(fileHash))); + await prottle( + requestWindowSize, + remoteFiles.map((fileHash) => () => this.pinFileHash(fileHash)), + ); } - return remoteFiles.map(remoteFile => Ipfs.ipfsHashToBytes32(remoteFile.hash)); + return remoteFiles.map((remoteFile) => Ipfs.ipfsHashToBytes32(remoteFile.hash)); } /** @@ -187,46 +195,43 @@ export class Ipfs extends Logger implements DfsInterface { this.log(`Getting IPFS Hash ${ipfsHash}`, 'debug'); if (this.cache) { - let buffer = await this.cache.get(ipfsHash); + const buffer = await this.cache.get(ipfsHash); if (buffer) { if (returnBuffer) { return Buffer.from(buffer); - } else { - return Buffer.from(buffer).toString('binary'); } + return Buffer.from(buffer).toString('binary'); } } const timeout = new Promise((resolve, reject) => { - let wait = setTimeout(() => { + const wait = setTimeout(() => { clearTimeout(wait); - reject(new Error(`error while getting ipfs hash ${ipfsHash}: rejected after ${ IPFS_TIMEOUT }ms`)); - }, IPFS_TIMEOUT) + reject(new Error(`error while getting ipfs hash ${ipfsHash}: rejected after ${IPFS_TIMEOUT}ms`)); + }, IPFS_TIMEOUT); }); const getRemoteHash = this.remoteNode.files.cat(ipfsHash) .then((buffer: any) => { - let fileBuffer = buffer; + const fileBuffer = buffer; const ret = fileBuffer.toString('binary'); if (this.cache) { this.cache.add(ipfsHash, fileBuffer); } if (returnBuffer) { return fileBuffer; - } else { - return ret; } + return ret; }) - .catch((ex: any) => { + .catch(() => { throw new Error(`error while getting ipfs hash ${ipfsHash}`); - }) - ; + }); return Promise.race([ getRemoteHash, - timeout + timeout, ]); - }; + } /** * @brief pins file hashes on ipfs cluster @@ -287,8 +292,8 @@ export class Ipfs extends Logger implements DfsInterface { const ipfsAuthHeader = await utils.getSmartAgentAuthHeaders(this.runtime); this.remoteNode.provider.headers.authorization = ipfsAuthHeader; setTimeout(() => { - delete this.remoteNode.provider.headers.authorization - }, 60 * 1000) + delete this.remoteNode.provider.headers.authorization; + }, 60 * 1000); } } } diff --git a/src/dfs/ipld.spec.ts b/src/dfs/ipld.spec.ts index 15ebe4c5..3532b8d4 100644 --- a/src/dfs/ipld.spec.ts +++ b/src/dfs/ipld.spec.ts @@ -19,31 +19,18 @@ import 'mocha'; import { expect } from 'chai'; -import bs58 = require('bs58'); -import Web3 = require('web3'); -import { - Envelope, - KeyProvider, -} from '@evan.network/dbcp'; - -import { accounts } from '../test/accounts'; -import { Aes } from '../encryption/aes'; -import { configTestcore as config } from '../config-testcore'; import { CryptoProvider } from '../encryption/crypto-provider'; -import { Ipld } from './ipld' -import { TestUtils } from '../test/test-utils' +import { Ipld } from './ipld'; +import { TestUtils } from '../test/test-utils'; -const sampleKey = '346c22768f84f3050f5c94cec98349b3c5cbfa0b7315304e13647a49181fd1ef'; -let keyProvider; -describe('IPLD handler', function() { +describe('IPLD handler', function test() { this.timeout(300000); - let node; let ipld: Ipld; let ipfs; let cryptoProvider: CryptoProvider; - let helperWeb3 = TestUtils.getWeb3(); + const helperWeb3 = TestUtils.getWeb3(); before(async () => { // create new ipld handler on ipfs node @@ -57,14 +44,14 @@ describe('IPLD handler', function() { }); describe('when creating a graph', () => { - it('should return an IPFS file hash with 32 bytes length when storing', async() => { + it('should return an IPFS file hash with 32 bytes length when storing', async () => { const sampleObject = { personalInfo: { firstName: 'eris', }, }; - const stored = await ipld.store(Object.assign({}, sampleObject)); + const stored = await ipld.store({ ...sampleObject }); expect(stored).to.match(/0x[0-9a-f]{64}/); }); @@ -75,7 +62,7 @@ describe('IPLD handler', function() { }, }; - const stored = await ipld.store(Object.assign({}, sampleObject)); + const stored = await ipld.store({ ...sampleObject }); const loaded = await ipld.getLinkedGraph(stored, ''); expect(loaded).not.to.be.undefined; Ipld.purgeCryptoInfo(loaded); @@ -89,7 +76,7 @@ describe('IPLD handler', function() { }, }; - const stored = await ipld.store(Object.assign({}, sampleObject)); + const stored = await ipld.store({ ...sampleObject }); const loaded = await ipld.getLinkedGraph(stored, ''); expect(loaded).not.to.be.undefined; Ipld.purgeCryptoInfo(loaded); @@ -105,11 +92,11 @@ describe('IPLD handler', function() { }, }; const sub = { - contracts: ['0x01', '0x02', '0x03'] + contracts: ['0x01', '0x02', '0x03'], }; const extended = await ipld.set(sampleObject, 'dapps', sub); - const extendedstored = await ipld.store(Object.assign({}, extended)); + const extendedstored = await ipld.store({ ...extended }); const loaded = await ipld.getLinkedGraph(extendedstored, ''); // unlinked (root data) @@ -135,22 +122,20 @@ describe('IPLD handler', function() { }, }; const sub = { - contracts: ['0x01', '0x02', '0x03'] + contracts: ['0x01', '0x02', '0x03'], }; const subSub = { contracts: '0x02', }; - const stored = await ipld.store(Object.assign({}, sampleObject)); - const loadedStored = await ipld.getLinkedGraph(stored, ''); // add lv1 const plusSub = await ipld.set(sampleObject, 'dapps', sub); - const plusSubstored = await ipld.store(Object.assign({}, plusSub)); + const plusSubstored = await ipld.store({ ...plusSub }); const loadedSub = await ipld.getLinkedGraph(plusSubstored, ''); // add lv2 const plusSubSub = await ipld.set(loadedSub, 'dapps/favorites', subSub); - const plusSubSubstored = await ipld.store(Object.assign({}, plusSubSub)); + const plusSubSubstored = await ipld.store({ ...plusSubSub }); const loadedFull = await ipld.getLinkedGraph(plusSubSubstored, ''); // unlinked (root data) @@ -177,13 +162,13 @@ describe('IPLD handler', function() { }, }; const sub = { - contracts: ['0x01', '0x02', '0x03'] + contracts: ['0x01', '0x02', '0x03'], }; const cryptor = cryptoProvider.getCryptorByCryptoAlgo('aes'); const cryptoInfo = cryptor.getCryptoInfo(helperWeb3.utils.soliditySha3('context sample')); const extended = await ipld.set(sampleObject, 'dapps', sub, false, cryptoInfo); - const extendedstored = await ipld.store(Object.assign({}, extended)); + const extendedstored = await ipld.store({ ...extended }); const loaded = await ipld.getLinkedGraph(extendedstored, ''); // unlinked (root data) @@ -211,22 +196,20 @@ describe('IPLD handler', function() { }, }; const sub = { - contracts: ['0x01', '0x02', '0x03'] + contracts: ['0x01', '0x02', '0x03'], }; const subSub = { contracts: '0x02', }; - const stored = await ipld.store(Object.assign({}, sampleObject)); - const loadedStored = await ipld.getLinkedGraph(stored, ''); // add lv1 const plusSub = await ipld.set(sampleObject, 'dapps', sub); - const plusSubstored = await ipld.store(Object.assign({}, plusSub)); + const plusSubstored = await ipld.store({ ...plusSub }); const loadedSub = await ipld.getLinkedGraph(plusSubstored, ''); // add lv2 const plusSubSub = await ipld.set(loadedSub, 'dapps/favorites', subSub); - const plusSubSubstored = await ipld.store(Object.assign({}, plusSubSub)); + const plusSubSubstored = await ipld.store({ ...plusSubSub }); const loadedFull = await ipld.getLinkedGraph(plusSubSubstored, ''); // lv1 is linked @@ -252,26 +235,22 @@ describe('IPLD handler', function() { }, }; const sub = { - contracts: ['0x01', '0x02', '0x03'] + contracts: ['0x01', '0x02', '0x03'], }; const subSub = { contracts: '0x02', }; - const stored = await ipld.store(Object.assign({}, sampleObject)); - const loadedStored = await ipld.getLinkedGraph(stored, ''); // add lv1 const plusSub = await ipld.set(sampleObject, 'dapps', sub); - const plusSubstored = await ipld.store(Object.assign({}, plusSub)); + const plusSubstored = await ipld.store({ ...plusSub }); const loadedSub = await ipld.getLinkedGraph(plusSubstored, ''); // add lv2 const plusSubSub = await ipld.set(loadedSub, 'dapps/favorites', subSub); - const plusSubSubstored = await ipld.store(Object.assign({}, plusSubSub)); + const plusSubSubstored = await ipld.store({ ...plusSubSub }); const loadedFull = await ipld.getResolvedGraph(plusSubSubstored, ''); - const resolved = await ipld.getResolvedGraph(plusSubSubstored, ''); - // lv1 is not linked expect(loadedFull).to.haveOwnProperty('dapps'); expect(loadedFull.dapps).to.haveOwnProperty('/'); @@ -291,13 +270,13 @@ describe('IPLD handler', function() { const sampleObject = { personalInfo: { firstName: 'eris', - titles: ['eris'] + titles: ['eris'], }, }; const sub = { - contracts: ['0x01', '0x02', '0x03'] + contracts: ['0x01', '0x02', '0x03'], }; - const stored = await ipld.store(Object.assign({}, sampleObject)); + const stored = await ipld.store({ ...sampleObject }); const graph = await ipld.getLinkedGraph(stored); await ipld.set(graph, 'dapps', sub); @@ -310,7 +289,7 @@ describe('IPLD handler', function() { const expected = { personalInfo: { firstName: 'eris', - titles: ['eris'] + titles: ['eris'], }, dapps: { '/': { @@ -329,26 +308,23 @@ describe('IPLD handler', function() { const sampleObject = { personalInfo: { firstName: 'eris', - titles: ['eris'] + titles: ['eris'], }, }; const sub = { - contracts: ['0x01', '0x02', '0x03'] + contracts: ['0x01', '0x02', '0x03'], }; - const stored = await ipld.store(Object.assign({}, sampleObject)); const extended = await ipld.set(sampleObject, 'dapps', sub); - const extendedstored = await ipld.store(Object.assign({}, extended)); + const extendedstored = await ipld.store({ ...extended }); const loaded = await ipld.getLinkedGraph(extendedstored, ''); - const subModified = Object.assign({}, sub); + const subModified = { ...sub }; subModified.contracts.push('0x04'); const updated = await ipld.set(loaded, 'dapps', subModified); - const updatedstored = await ipld.store(Object.assign({}, updated)); - const updatedloaded = await ipld.getLinkedGraph(updatedstored, ''); - const updatedStored = await ipld.store(Object.assign({}, updated)); + const updatedStored = await ipld.store({ ...updated }); const loadedUpdated = await ipld.getResolvedGraph(updatedStored, ''); // linked (root data) access @@ -358,45 +334,40 @@ describe('IPLD handler', function() { }); it('should be able to update different ipld graphs with different keys at the same time', - async () => { - let lastKey; - async function updateGraph() { + async () => { + async function updateGraph() { // shadow ipld with a new one with another key - const defaultCryptoAlgo = 'aes'; - const localIpld = await TestUtils.getIpld(ipfs); - const sampleObject = { - personalInfo: { - firstName: 'eris', - titles: ['eris'] - }, - }; - const sub = { - contracts: ['0x01', '0x02', '0x03'] - }; + const localIpld = await TestUtils.getIpld(ipfs); + const sampleObject = { + personalInfo: { + firstName: 'eris', + titles: ['eris'], + }, + }; + const sub = { + contracts: ['0x01', '0x02', '0x03'], + }; - const stored = await localIpld.store(Object.assign({}, sampleObject)); - const extended = await localIpld.set(sampleObject, 'dapps', sub); - const extendedstored = await localIpld.store(Object.assign({}, extended)); - const loaded = await localIpld.getLinkedGraph(extendedstored, ''); + const extended = await localIpld.set(sampleObject, 'dapps', sub); + const extendedstored = await localIpld.store({ ...extended }); + const loaded = await localIpld.getLinkedGraph(extendedstored, ''); - const subModified = Object.assign({}, sub); - subModified.contracts.push('0x04'); - const updated = await localIpld.set(loaded, 'dapps', subModified); - const updatedstored = await localIpld.store(Object.assign({}, updated)); - const updatedloaded = await localIpld.getLinkedGraph(updatedstored, ''); + const subModified = { ...sub }; + subModified.contracts.push('0x04'); + const updated = await localIpld.set(loaded, 'dapps', subModified); - const updatedStored = await localIpld.store(Object.assign({}, updated)); - const loadedUpdated = await localIpld.getResolvedGraph(updatedStored, ''); + const updatedStored = await localIpld.store({ ...updated }); + const loadedUpdated = await localIpld.getResolvedGraph(updatedStored, ''); - // linked (root data) access - expect(loadedUpdated).not.to.be.undefined; - expect(Array.isArray(loadedUpdated.dapps['/'].contracts)).to.be.true; - expect(loadedUpdated.dapps['/'].contracts.length).to.eq(subModified.contracts.length); - } + // linked (root data) access + expect(loadedUpdated).not.to.be.undefined; + expect(Array.isArray(loadedUpdated.dapps['/'].contracts)).to.be.true; + expect(loadedUpdated.dapps['/'].contracts.length).to.eq(subModified.contracts.length); + } - await Promise.all([...new Array(10)].map(() => updateGraph())); - }); + await Promise.all([...new Array(10)].map(() => updateGraph())); + }); }); describe('when deleting nodes', () => { @@ -443,15 +414,15 @@ describe('IPLD handler', function() { let updated; // store initial - stored = await ipld.store(Object.assign({}, initialTree)); + stored = await ipld.store({ ...initialTree }); loaded = await ipld.getLinkedGraph(stored, ''); // add subtree updated = await ipld.set(loaded, 'gods/eris', eris); - stored = await ipld.store(Object.assign({}, updated)); + stored = await ipld.store({ ...updated }); loaded = await ipld.getLinkedGraph(stored, ''); updated = await ipld.set(loaded, 'gods/eris/details', erisDetails); - stored = await ipld.store(Object.assign({}, updated)); + stored = await ipld.store({ ...updated }); loaded = await ipld.getLinkedGraph(stored, ''); // check loaded @@ -524,7 +495,7 @@ describe('IPLD handler', function() { // delete topmost link sampleGraph = await createSampleGraph(); updated = await ipld.remove(sampleGraph, 'gods/eris'); - stored = await ipld.store(Object.assign({}, updated)); + stored = await ipld.store({ ...updated }); loaded = await ipld.getResolvedGraph(stored, ''); expect(loaded).not.to.be.undefined; Ipld.purgeCryptoInfo(loaded); @@ -533,7 +504,7 @@ describe('IPLD handler', function() { // delete topmost link sampleGraph = await createSampleGraph(); updated = await ipld.remove(sampleGraph, 'gods/eris/details'); - stored = await ipld.store(Object.assign({}, updated)); + stored = await ipld.store({ ...updated }); loaded = await ipld.getResolvedGraph(stored, ''); expect(loaded).not.to.be.undefined; Ipld.purgeCryptoInfo(loaded); @@ -562,14 +533,11 @@ describe('IPLD handler', function() { }, }, }; - let stored; - let loaded; - let updated; const sampleGraph = await createSampleGraph(); - updated = await ipld.remove(sampleGraph, deletion); - stored = await ipld.store(Object.assign({}, updated)); - loaded = await ipld.getResolvedGraph(stored, ''); + const updated = await ipld.remove(sampleGraph, deletion); + const stored = await ipld.store({ ...updated }); + const loaded = await ipld.getResolvedGraph(stored, ''); expect(loaded).not.to.be.undefined; Ipld.purgeCryptoInfo(loaded); expect(loaded).to.deep.eq(expectedGraph); @@ -606,7 +574,7 @@ describe('IPLD handler', function() { const sampleGraph = await createSampleGraph(); updated = await ipld.remove(sampleGraph, deletion); - stored = await ipld.store(Object.assign({}, updated)); + stored = await ipld.store({ ...updated }); loaded = await ipld.getResolvedGraph(stored, ''); // add lv2 @@ -615,7 +583,7 @@ describe('IPLD handler', function() { const loadedSub = await ipld.getLinkedGraph(plusSub, ''); updated = await ipld.remove(loadedSub, 'titans/prometeus'); - stored = await ipld.store(Object.assign({}, updated)); + stored = await ipld.store({ ...updated }); loaded = await ipld.getResolvedGraph(stored, ''); Ipld.purgeCryptoInfo(loaded); expect(loaded).to.deep.eq(expectedGraph); @@ -656,7 +624,7 @@ describe('IPLD handler', function() { const sampleGraph = await createSampleGraph(); updated = await ipld.remove(sampleGraph, deletion); - stored = await ipld.store(Object.assign({}, updated)); + stored = await ipld.store({ ...updated }); loaded = await ipld.getResolvedGraph(stored, ''); // add lv3 @@ -666,7 +634,7 @@ describe('IPLD handler', function() { const loadedSub = await ipld.getLinkedGraph(plusSub, ''); updated = await ipld.remove(loadedSub, 'titans/prometeus/punishment'); - stored = await ipld.store(Object.assign({}, updated)); + stored = await ipld.store({ ...updated }); loaded = await ipld.getResolvedGraph(stored, ''); Ipld.purgeCryptoInfo(loaded); expect(loaded).to.deep.eq(expectedGraph); @@ -705,15 +673,15 @@ describe('IPLD handler', function() { const sampleGraph = await createSampleGraph(); updated = await ipld.remove(sampleGraph, deletion); - stored = await ipld.store(Object.assign({}, updated)); + stored = await ipld.store({ ...updated }); loaded = await ipld.getResolvedGraph(stored, ''); // add lv3 - let plusSub = await ipld.set(sampleGraph, 'titans/prometeus', { punishment: { animal: 'raven' } }); + const plusSub = await ipld.set(sampleGraph, 'titans/prometeus', { punishment: { animal: 'raven' } }); const loadedSub = await ipld.getLinkedGraph(plusSub, ''); updated = await ipld.remove(loadedSub, 'titans/prometeus/punishment'); - stored = await ipld.store(Object.assign({}, updated)); + stored = await ipld.store({ ...updated }); loaded = await ipld.getResolvedGraph(stored, ''); Ipld.purgeCryptoInfo(loaded); expect(loaded).to.deep.eq(expectedGraph); @@ -738,7 +706,7 @@ describe('IPLD handler', function() { titans: { prometeus: { '/': { - 'punishment': { + punishment: { animal: 'raven', }, }, @@ -751,15 +719,15 @@ describe('IPLD handler', function() { const sampleGraph = await createSampleGraph(); updated = await ipld.remove(sampleGraph, deletion); - stored = await ipld.store(Object.assign({}, updated)); + stored = await ipld.store({ ...updated }); loaded = await ipld.getResolvedGraph(stored, ''); // add lv3 - let plusSub = await ipld.set(sampleGraph, 'titans/prometeus', { punishment: { animal: 'raven' } }); + const plusSub = await ipld.set(sampleGraph, 'titans/prometeus', { punishment: { animal: 'raven' } }); const loadedSub = await ipld.getLinkedGraph(plusSub, ''); updated = await ipld.remove(loadedSub, 'gods/eris/details'); - stored = await ipld.store(Object.assign({}, updated)); + stored = await ipld.store({ ...updated }); loaded = await ipld.getResolvedGraph(stored, ''); Ipld.purgeCryptoInfo(loaded); expect(loaded).to.deep.eq(expectedGraph); diff --git a/src/dfs/ipld.ts b/src/dfs/ipld.ts index c5eb1f10..c6db9647 100644 --- a/src/dfs/ipld.ts +++ b/src/dfs/ipld.ts @@ -17,14 +17,8 @@ the following URL: https://evan.network/license/ */ -import Graph = require('ipld-graph-builder'); -import bs58 = require('bs58'); -import * as https from 'https'; -import _ = require('lodash'); - import { CryptoInfo, - Cryptor, Envelope, KeyProviderInterface, Logger, @@ -32,15 +26,20 @@ import { NameResolver, } from '@evan.network/dbcp'; -import { Ipfs } from '../dfs/ipfs'; +import { Ipfs } from './ipfs'; import { CryptoProvider } from '../encryption/crypto-provider'; +import Graph = require('ipld-graph-builder'); +import _ = require('lodash'); +import bs58 = require('bs58'); + const IPLD_TIMEOUT = 120000; function rebuffer(toBuffer) { Object.keys(toBuffer).forEach((key) => { if (key === '/') { + // eslint-disable-next-line no-param-reassign toBuffer[key] = Buffer.from(toBuffer[key].data); } else if (typeof toBuffer[key] === 'object' && toBuffer[key] !== null) { rebuffer(toBuffer[key]); @@ -65,17 +64,26 @@ export interface IpldOptions extends LoggerOptions { * @class Ipld IPFS helper class */ export class Ipld extends Logger { - graph: Graph; - ipfs: Ipfs; - keyProvider: KeyProviderInterface; - cryptoProvider: CryptoProvider; - originator: string; - defaultCryptoAlgo: string; - nameResolver: NameResolver; - hashLog: string[] = []; + public graph: Graph; + + public ipfs: Ipfs; + + public keyProvider: KeyProviderInterface; + + public cryptoProvider: CryptoProvider; + + public originator: string; + + public defaultCryptoAlgo: string; + + public nameResolver: NameResolver; + + public hashLog: string[] = []; + + private readonly dagOptions = { format: 'dag-pb' }; - private readonly dagOptions = { format: 'dag-pb', }; private readonly encodingUnencrypted = 'utf-8'; + private readonly encodingEncrypted = 'hex'; /** @@ -86,6 +94,7 @@ export class Ipld extends Logger { public static purgeCryptoInfo(toPurge: any): void { Object.keys(toPurge).forEach((key) => { if (key === 'cryptoInfo') { + // eslint-disable-next-line no-param-reassign delete toPurge.cryptoInfo; } else if (typeof toPurge[key] === 'object' && toPurge[key] !== null) { this.purgeCryptoInfo(toPurge[key]); @@ -93,10 +102,10 @@ export class Ipld extends Logger { }); } - constructor(options: IpldOptions) { + public constructor(options: IpldOptions) { super(options); this.ipfs = options.ipfs; - this.graph = new Graph({ get: null, put: null, }); + this.graph = new Graph({ get: null, put: null }); this.keyProvider = options.keyProvider; this.cryptoProvider = options.cryptoProvider; this.originator = options.originator; @@ -104,9 +113,9 @@ export class Ipld extends Logger { this.nameResolver = options.nameResolver; // overwrite dag.put and dag.get if cryptor was provided - const originalDagPut = this.graph._dag.put; + // eslint-disable-next-line no-underscore-dangle this.graph._dag.put = async (...args) => { - const data = args[0]; + let data = args[0]; if (data.cryptoInfo || this.defaultCryptoAlgo) { let cryptor; let cryptoInfo; @@ -119,35 +128,34 @@ export class Ipld extends Logger { cryptoInfo = cryptor.getCryptoInfo(this.originator); } const key = await this.keyProvider.getKey(cryptoInfo); - const encrypted = await cryptor.encrypt(args[0], { key, }) - args[0] = encrypted.toString(this.encodingEncrypted); + const encrypted = await cryptor.encrypt(args[0], { key }); const envelope: Envelope = { private: encrypted, cryptoInfo, }; - args[0] = Buffer.from(JSON.stringify(envelope)); + data = Buffer.from(JSON.stringify(envelope)); } // add file to ipfs instead of dag put because js-ipfs-api don't supports dag at the moment - return this.ipfs.add('dag', args[0]) + return this.ipfs.add('dag', data) .then((hash) => { this.hashLog.push(hash); const bufferHash = bs58.decode(Ipfs.bytes32ToIpfsHash(hash)); - const dagHash = bs58.encode(bufferHash); return bufferHash; }); }; - const originalDagGet = this.graph._dag.get; + // eslint-disable-next-line no-underscore-dangle this.graph._dag.get = (...args) => { const timeout = new Promise((resolve, reject) => { - let wait = setTimeout(() => { + const wait = setTimeout(() => { clearTimeout(wait); reject(new Error('timeout reached')); - }, IPLD_TIMEOUT) - }) + }, IPLD_TIMEOUT); + }); this.log(`Getting IPLD Hash ${bs58.encode(args[0])}`, 'debug'); // add file to ipfs instead of dag put because js-ipfs-api don't supports dag at the moment const getHash = this.ipfs.get(bs58.encode(args[0])) + // eslint-disable-next-line consistent-return .then(async (dag) => { if (this.defaultCryptoAlgo) { const envelope: Envelope = JSON.parse(dag.toString('utf-8')); @@ -155,24 +163,22 @@ export class Ipld extends Logger { const key = await this.keyProvider.getKey(envelope.cryptoInfo); if (!key) { return {}; - } else { - - const decryptedObject = await cryptor.decrypt( - Buffer.from(envelope.private, this.encodingEncrypted), { key, }); - rebuffer(decryptedObject); - if (typeof decryptedObject === 'object') { - // keep crypto info for later re-encryption - decryptedObject.cryptoInfo = envelope.cryptoInfo; - } - return decryptedObject; } + const decryptedObject = await cryptor.decrypt( + Buffer.from(envelope.private, this.encodingEncrypted), { key }, + ); + rebuffer(decryptedObject); + if (typeof decryptedObject === 'object') { + // keep crypto info for later re-encryption + decryptedObject.cryptoInfo = envelope.cryptoInfo; + } + return decryptedObject; } - }) - ; + }); return Promise.race([ getHash, - timeout - ]) + timeout, + ]); }; } @@ -183,7 +189,7 @@ export class Ipld extends Logger { * @param {string} path path in the tree * @return {Promise} linked graph. */ - async getLinkedGraph(graphReference: string | Buffer | any, path = ''): Promise { + public async getLinkedGraph(graphReference: string | Buffer | any, path = ''): Promise { let graphObject; if (typeof graphReference === 'string') { // fetch ipfs file @@ -195,30 +201,30 @@ export class Ipld extends Logger { const cryptor = this.cryptoProvider.getCryptorByCryptoInfo(envelope.cryptoInfo); const key = await this.keyProvider.getKey(envelope.cryptoInfo); const decryptedObject = await cryptor.decrypt( - Buffer.from(envelope.private, this.encodingEncrypted), { key, }); + Buffer.from(envelope.private, this.encodingEncrypted), { key }, + ); rebuffer(decryptedObject); graphObject = decryptedObject; } else { - graphObject = { '/': Buffer.from(ipfsFile, this.encodingUnencrypted), }; + graphObject = { '/': Buffer.from(ipfsFile, this.encodingUnencrypted) }; } } else if (Buffer.isBuffer(graphReference)) { - graphObject = { '/': graphReference, }; + graphObject = { '/': graphReference }; } else { graphObject = graphReference; } if (!path) { const tree = await this.graph.tree(graphObject, 0); return tree['/'] || tree; + } + const element = await this.graph.get(graphObject, path); + if (element) { + this.log(`Got Linked Graph Path -> ${path} Element`, 'debug'); } else { - const element = await this.graph.get(graphObject, path) - if (element) { - this.log(`Got Linked Graph Path -> ${path} Element`, 'debug'); - } else { - this.log(`Could not get Linked Graph Path -> ${path} Element`, 'debug'); - } - - return element; + this.log(`Could not get Linked Graph Path -> ${path} Element`, 'debug'); } + + return element; } /** @@ -230,9 +236,9 @@ export class Ipld extends Logger { * (default: 10) * @return {Promise} resolved graph */ - async getResolvedGraph(graphReference: string | Buffer | any, path = '', depth = 10): Promise { + public async getResolvedGraph(graphReference: string | Buffer | any, path = '', depth = 10): Promise { const treeNode = await this.getLinkedGraph(graphReference, path); - return await this.graph.tree(treeNode, depth, true); + return this.graph.tree(treeNode, depth, true); } /** @@ -241,19 +247,18 @@ export class Ipld extends Logger { * @param {any} toSet tree to store * @return {Promise} hash reference to a tree with with merklefied links */ - async store(toSet: any): Promise { + public async store(toSet: any): Promise { const cryptoInfo = { - algorithm: 'unencrypted' - } + algorithm: 'unencrypted', + }; const treeToStore = _.cloneDeep(toSet); - const [rootObject, key] = await Promise.all([ - // get final tree - this.graph.flush(treeToStore, Object.assign({}, this.dagOptions, { cryptoInfo, })), - // encrypt dag and put in envelope - this.keyProvider.getKey(cryptoInfo), - ]); + // get final tree + const rootObject = await this.graph.flush( + treeToStore, { ...this.dagOptions, cryptoInfo }, + ); const envelope: Envelope = { - private: Buffer.from(JSON.stringify(rootObject), this.encodingUnencrypted).toString(this.encodingEncrypted), + private: Buffer.from(JSON.stringify(rootObject), this.encodingUnencrypted) + .toString(this.encodingEncrypted), cryptoInfo, }; @@ -273,11 +278,18 @@ export class Ipld extends Logger { * @param {CryptoInfo} cryptoInfo crypto info for encrypting subtree * @return {Promise} tree with merklefied links */ - async set(tree: any, path: string, subtree: any, plainObject = false, cryptoInfo?: CryptoInfo): Promise { - if (cryptoInfo && typeof subtree === 'object') { - subtree.cryptoInfo = cryptoInfo; + public async set( + tree: any, + path: string, + subtree: any, + plainObject = false, + cryptoInfo?: CryptoInfo, + ): Promise { + const subtreeParam = subtree; + if (cryptoInfo && typeof subtreeParam === 'object') { + subtreeParam.cryptoInfo = cryptoInfo; } - const graphTree = await this.graph.set(tree, path, subtree, plainObject); + const graphTree = await this.graph.set(tree, path, subtreeParam, plainObject); return graphTree; } @@ -288,15 +300,16 @@ export class Ipld extends Logger { * @param {string} path path of inserted element * @return {Promise} tree with merklefied links */ - async remove(tree: any, path: string): Promise { + public async remove(tree: any, path: string): Promise { const splitPath = path.split('/'); const node = splitPath[splitPath.length - 1]; const toTraverse = splitPath.slice(0, -1); let currentNode; - let currentTree + let currentTree; let linkedParent = tree; // find next linked node + // eslint-disable-next-line no-cond-assign while (currentNode = toTraverse.pop()) { currentTree = await this.getLinkedGraph(tree, toTraverse.join('/')); if (currentTree[currentNode]['/']) { @@ -314,19 +327,17 @@ export class Ipld extends Logger { let splitPathInParent; if (toTraverse.length) { pathInParent = path.replace(`${toTraverse.join('/')}/`, ''); - splitPathInParent = pathInParent.split('/').slice(1, -1); // skip parent prop, skip last + splitPathInParent = pathInParent.split('/').slice(1, -1); // skip parent prop, skip last + } else if (linkedParent === tree) { + // entire graph is plain object, current linkedParent is entire tree + splitPathInParent = path.split('/').slice(0, -1); // skip last } else { - pathInParent = path; - if (linkedParent === tree) { - // entire graph is plain object, current linkedParent is entire tree - splitPathInParent = path.split('/').slice(0, -1); // skip last - } else { - // linkedParent points to found node, node name is still in path - splitPathInParent = path.split('/').slice(1, -1); // skip parent prop, skip last - } + // linkedParent points to found node, node name is still in path + splitPathInParent = path.split('/').slice(1, -1); // skip parent prop, skip last } let nodeInParentPath = linkedParent; let nodeNameInParentPath; + // eslint-disable-next-line no-cond-assign while (nodeNameInParentPath = splitPathInParent.pop()) { nodeInParentPath = nodeInParentPath[nodeNameInParentPath]; } @@ -334,13 +345,12 @@ export class Ipld extends Logger { if (toTraverse.length) { // set updated linked node in entire graph - return await this.graph.set(tree, `${toTraverse.join('/')}/${currentNode}`, linkedParent); - } else { - // flush graph, return linked graph object - const cryptor = this.cryptoProvider.getCryptorByCryptoAlgo(this.defaultCryptoAlgo); - const cryptoInfo = cryptor.getCryptoInfo(this.originator); - const flushed = await this.graph.flush(tree, this.dagOptions, this.dagOptions, { cryptoInfo, }); - return this.getLinkedGraph(flushed); + return this.graph.set(tree, `${toTraverse.join('/')}/${currentNode}`, linkedParent); } + // flush graph, return linked graph object + const cryptor = this.cryptoProvider.getCryptorByCryptoAlgo(this.defaultCryptoAlgo); + const cryptoInfo = cryptor.getCryptoInfo(this.originator); + const flushed = await this.graph.flush(tree, this.dagOptions, this.dagOptions, { cryptoInfo }); + return this.getLinkedGraph(flushed); } } diff --git a/src/did/did.spec.ts b/src/did/did.spec.ts new file mode 100644 index 00000000..87389f6b --- /dev/null +++ b/src/did/did.spec.ts @@ -0,0 +1,219 @@ +/* + Copyright (C) 2018-present evan GmbH. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License, version 3, + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see http://www.gnu.org/licenses/ or + write to the Free Software Foundation, Inc., 51 Franklin Street, + Fifth Floor, Boston, MA, 02110-1301 USA, or download the license from + the following URL: https://evan.network/license/ +*/ + +import 'mocha'; +import * as chaiAsPromised from 'chai-as-promised'; +import { expect, use } from 'chai'; + +import { accounts } from '../test/accounts'; +import { TestUtils } from '../test/test-utils'; +import { + DigitalTwin, + DigitalTwinOptions, + Runtime, +} from '../index'; + +use(chaiAsPromised); + +describe('DID Resolver', function test() { + this.timeout(600000); + let accounts0Identity: string; + let accounts0Did: string; + let runtimes: Runtime[]; + + before(async () => { + runtimes = await Promise.all([ + TestUtils.getRuntime(accounts[0], null, { useIdentity: true }), + TestUtils.getRuntime(accounts[1], null, { useIdentity: true }), + ]); + accounts0Identity = await runtimes[0].verifications.getIdentityForAccount(accounts[0], true); + accounts0Did = await runtimes[0].did.convertIdentityToDid(accounts0Identity); + }); + + describe('when storing did documents for account identities', async () => { + it('allows to store a DID document for the own identity', async () => { + const document = await runtimes[0].did.getDidDocumentTemplate(); + const promise = runtimes[0].did.setDidDocument(accounts0Did, document); + await expect(promise).not.to.be.rejected; + }); + + it('can get retrieve an account identities DID document', async () => { + const document = await runtimes[0].did.getDidDocumentTemplate(); + await runtimes[0].did.setDidDocument(accounts0Did, document); + const retrieved = await runtimes[0].did.getDidDocument(accounts0Did); + expect(retrieved).to.deep.eq(document); + }); + + it('allows to get a DID document of another identity', async () => { + const document = await runtimes[0].did.getDidDocumentTemplate(); + await runtimes[0].did.setDidDocument(accounts0Did, document); + const retrieved = await runtimes[1].did.getDidDocument(accounts0Did); + + expect(retrieved).to.deep.eq(document); + }); + + it('does not allow to store a DID document for another identity', async () => { + const document = await runtimes[0].did.getDidDocumentTemplate(); + const accounts1Identity = await runtimes[0].verifications.getIdentityForAccount( + accounts[1], + true, + ); + const accounts1Did = await runtimes[0].did.convertIdentityToDid(accounts1Identity); + const promise = runtimes[0].did.setDidDocument(accounts1Did, document); + await expect(promise).to.be.rejectedWith(/^could not estimate gas usage for setDidDocument/); + }); + + it('allows to define services in a DID document', async () => { + const document = await runtimes[0].did.getDidDocumentTemplate(); + await runtimes[0].did.setDidDocument(accounts0Did, document); + + // set new service + const random = Math.floor(Math.random() * 1e9); + const service = [{ + id: `${accounts0Did}#randomService`, + type: `randomService-${random}`, + serviceEndpoint: `https://openid.example.com/${random}`, + }]; + await runtimes[0].did.setService(accounts0Did, service); + + expect(await runtimes[0].did.getService(accounts0Did)) + .to.deep.eq(service); + expect(await runtimes[0].did.getDidDocument(accounts0Did)) + .to.deep.eq({ ...document, service }); + }); + }); + + describe('when storing did documents for contract identities', () => { + const twinDescription = { + name: 'test twin', + description: 'twin from test run', + author: 'evan GmbH', + version: '0.1.0', + dbcpVersion: 2, + }; + + it('allows to store a DID document for the identity of an own contract', async () => { + const accountRuntime = await TestUtils.getRuntime(accounts[0]); + const twin = await DigitalTwin.create( + accountRuntime as DigitalTwinOptions, + { + accountId: accountRuntime.activeAccount, + containerConfig: null, + description: twinDescription, + }, + ); + const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( + await twin.getContractAddress(), true, + ); + const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); + + const controllerDid = await runtimes[0].did.convertIdentityToDid( + runtimes[0].activeIdentity, + ); + const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); + const document = await runtimes[0].did.getDidDocumentTemplate( + twinDid, controllerDid, controllerDidDocument.authentication[0], + ); + const promise = runtimes[0].did.setDidDocument(twinDid, document); + await expect(promise).not.to.be.rejected; + }); + + it('can get retrieve an contract identities DID document', async () => { + const accountRuntime = await TestUtils.getRuntime(accounts[0]); + const twin = await DigitalTwin.create( + accountRuntime as DigitalTwinOptions, + { + accountId: accountRuntime.activeAccount, + containerConfig: null, + description: twinDescription, + }, + ); + const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( + await twin.getContractAddress(), true, + ); + const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); + + const controllerDid = await runtimes[0].did.convertIdentityToDid( + runtimes[0].activeIdentity, + ); + const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); + const document = await runtimes[0].did.getDidDocumentTemplate( + twinDid, controllerDid, controllerDidDocument.authentication[0], + ); + await runtimes[0].did.setDidDocument(twinDid, document); + const retrieved = await runtimes[0].did.getDidDocument(twinDid); + expect(retrieved).to.deep.eq(document); + }); + + it('allows to get a DID document of another identity', async () => { + const accountRuntime = await TestUtils.getRuntime(accounts[0]); + const twin = await DigitalTwin.create( + accountRuntime as DigitalTwinOptions, + { + accountId: accountRuntime.activeAccount, + containerConfig: null, + description: twinDescription, + }, + ); + const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( + await twin.getContractAddress(), true, + ); + const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); + + const controllerDid = await runtimes[0].did.convertIdentityToDid( + runtimes[0].activeIdentity, + ); + const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); + const document = await runtimes[0].did.getDidDocumentTemplate( + twinDid, controllerDid, controllerDidDocument.authentication[0], + ); + await runtimes[0].did.setDidDocument(twinDid, document); + + const retrieved = await runtimes[1].did.getDidDocument(twinDid); + expect(retrieved).to.deep.eq(document); + }); + + it('does not allow to store a DID document for the identity of an own contract', async () => { + const accountRuntime = await TestUtils.getRuntime(accounts[0]); + const twin = await DigitalTwin.create( + accountRuntime as DigitalTwinOptions, + { + accountId: accountRuntime.activeAccount, + containerConfig: null, + description: twinDescription, + }, + ); + const twinIdentity = await runtimes[0].verifications.getIdentityForAccount( + await twin.getContractAddress(), true, + ); + const twinDid = await runtimes[0].did.convertIdentityToDid(twinIdentity); + + const controllerDid = await runtimes[0].did.convertIdentityToDid( + runtimes[0].activeIdentity, + ); + const controllerDidDocument = await runtimes[0].did.getDidDocument(accounts0Did); + const document = await runtimes[0].did.getDidDocumentTemplate( + twinDid, controllerDid, controllerDidDocument.authentication[0], + ); + const runtime1 = runtimes[1]; + const promise = runtime1.did.setDidDocument(twinDid, document); + await expect(promise).to.be.rejectedWith(/^could not estimate gas usage for setDidDocument/); + }); + }); +}); diff --git a/src/did/did.ts b/src/did/did.ts new file mode 100644 index 00000000..f39eeac8 --- /dev/null +++ b/src/did/did.ts @@ -0,0 +1,368 @@ +/* + Copyright (C) 2018-present evan GmbH. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License, version 3, + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see http://www.gnu.org/licenses/ or + write to the Free Software Foundation, Inc., 51 Franklin Street, + Fifth Floor, Boston, MA, 02110-1301 USA, or download the license from + the following URL: https://evan.network/license/ +*/ + +import { + ContractLoader, + DfsInterface, + Executor, + Logger, + LoggerOptions, +} from '@evan.network/dbcp'; + +import { + getEnvironment, + nullBytes32, +} from '../common/utils'; +import { + NameResolver, + SignerIdentity, +} from '../index'; + +const didRegEx = /^did:evan:(?:(testcore|core):)?(0x(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64}))$/; + +/** + * template for a new DID document, can be used as a starting point for building own documents + */ +export interface DidDocumentTemplate { + '@context': string; + id: string; + authentication: { + type: string; + publicKey: string; + } | { + type: string; + publicKey: string; + }[]; + publicKey?: { + id: string; + type: string; + publicKeyHex: string; + }[]; + service?: { + id: string; + type: string; + serviceEndpoint: string; + }[]; +} + +/** + * interface for services in DIDs + */ +export interface DidServiceEntry { + type: any; + serviceEndpoint: any; + '@context'?: any; + id?: any; + [id: string]: any; +} + +/** + * options for Did constructor + */ +export interface DidOptions extends LoggerOptions { + contractLoader: ContractLoader; + dfs: DfsInterface; + executor: Executor; + nameResolver: NameResolver; + signerIdentity: SignerIdentity; + web3: any; +} + +/** + * module for working with did resolver registry + * + * @class Did (name) + */ +export class Did extends Logger { + private cached: any; + + private options: DidOptions; + + /** + * Creates a new `Did` instance. + * + * @param {DidOptions} options runtime like options for `Did` + */ + public constructor(options: DidOptions) { + super(options as LoggerOptions); + this.options = options; + this.cached = {}; + } + + /** + * Converts given DID to a evan.network identity. + * + * @param {string} did a DID like + * "did:evan:testcore:0x000000000000000000000000000000000000001234" + * @return {Promise} evan.network identity like + * "0x000000000000000000000000000000000000001234" + */ + public async convertDidToIdentity(did: string): Promise { + const groups = await this.validateDidAndGetSections(did); + const [, didEnvironment = 'core', identity] = groups; + const environment = await this.getEnvironment(); + if ((environment === 'testcore' && didEnvironment !== 'testcore') + || (environment === 'core' && didEnvironment !== 'core')) { + throw new Error(`DIDs environment "${environment} does not match ${didEnvironment}`); + } + + return identity; + } + + /** + * Converts given evan.network identity hash to DID. + * + * @param {string} identity evan.network identity like + * "0x000000000000000000000000000000000000001234" + * @return {Promise} DID like + * did:evan:testcore:0x000000000000000000000000000000000000001234 + */ + public async convertIdentityToDid(identity: string): Promise { + return `did:evan:${await this.getDidInfix()}${identity}`; + } + + /** + * Get DID document for given DID. + * + * @param {string} did DID to fetch DID document for + * @return {Promise} a DID document that MAY resemble `DidDocumentTemplate` format + */ + public async getDidDocument(did: string): Promise { + let result = null; + const identity = this.padIdentity( + did + ? await this.convertDidToIdentity(did) + : this.options.signerIdentity.activeIdentity, + ); + const documentHash = await this.options.executor.executeContractCall( + await this.getRegistryContract(), + 'didDocuments', + identity, + ); + if (documentHash === nullBytes32) { + throw Error(`There is no DID document associated to ${did} yet`); + } + result = JSON.parse(await this.options.dfs.get(documentHash) as any); + result = await this.removePublicKeyTypeArray(result); + return result; + } + + /** + * Gets a DID document for currently configured account/identity pair. Notice, that this document + * may a complete DID document for currently configured active identity, a part of it or not + * matching it at all. You can use the result of this function to build a new DID document but + * should extend it or an existing DID document, if your details derive from default format. + * + * All three arguments are optional. When they are used, all of them have to be given and the + * result then describes a contracts DID document. If all of them are omitted the result describes + * an accounts DID document. + * + * @param {string} did (optional) contract DID + * @param {string} controllerDid (optional) controller of contracts identity (DID) + * @param {string} authenticationKey (optional) authentication key used for contract + * @return {Promise} a DID document template + */ + public async getDidDocumentTemplate( + did?: string, controllerDid?: string, authenticationKey?: string, + ): Promise { + if (did && controllerDid && authenticationKey) { + // use given key to create a contract DID document + return JSON.parse(`{ + "@context": "https://w3id.org/did/v1", + "id": "${did}", + "controller": "${controllerDid}", + "authentication": [ + "${authenticationKey}" + ] + }`); + } if (!(did || controllerDid || authenticationKey)) { + const identity = this.options.signerIdentity.activeIdentity; + const [didInfix, publicKey] = await Promise.all([ + this.getDidInfix(), + this.options.signerIdentity.getPublicKey( + this.options.signerIdentity.underlyingAccount, + ), + ]); + + return JSON.parse(`{ + "@context": "https://w3id.org/did/v1", + "id": "did:evan:${didInfix}${identity}", + "publicKey": [{ + "id": "did:evan:${didInfix}${identity}#key-1", + "type": "Secp256k1SignatureVerificationKey2018", + "publicKeyHex": "${publicKey}" + }], + "authentication": [ + "did:evan:${didInfix}${identity}#key-1" + ] + }`); + } + throw new Error('invalid config for template document'); + } + + /** + * Get service from DID document. + * + * @param {string} did DID name to get service for + * @return {Promise} service + */ + public async getService(did: string): Promise { + return (await this.getDidDocument(did)).service; + } + + /** + * Store given DID document for given DID. + * + * @param {string} did DID to store DID document for + * @param {any} document DID document to store + * @return {Promise} resolved when done + */ + public async setDidDocument(did: string, document: any): Promise { + const identity = this.padIdentity(did + ? await this.convertDidToIdentity(did) + : this.options.signerIdentity.activeIdentity); + const documentHash = await this.options.dfs.add( + 'did-document', Buffer.from(JSON.stringify(document), 'utf8'), + ); + await this.options.executor.executeContractTransaction( + await this.getRegistryContract(), + 'setDidDocument', + { from: this.options.signerIdentity.activeIdentity }, + identity, + documentHash, + ); + } + + /** + * Sets service in DID document. + * + * @param {string} did DID name to set service for + * @param {DidServiceEntry[] | DidServiceEntry} service service to set + * @return {Promise} resolved when done + */ + public async setService( + did: string, + service: DidServiceEntry[] | DidServiceEntry, + ): Promise { + await this.setDidDocument(did, { ...(await this.getDidDocument(did)), service }); + } + + /** + * Validates if a given DID is a valid evan DID. + * + * @param did DID to validate. + * @returns {Promise} If the DID is valid. + * @throws If the DID is not valid. + */ + public async validateDid(did: string): Promise { + await this.validateDidAndGetSections(did); + } + + /** + * Get environment dependent DID infix ('testcore:' || ''). Result is cached. + * + * @return {Promise} DID infix + */ + private async getDidInfix(): Promise { + if (typeof this.cached.didInfix === 'undefined') { + this.cached.didInfix = (await this.getEnvironment()) === 'testcore' ? 'testcore:' : ''; + } + return this.cached.didInfix; + } + + /** + * Get current environment ('testcore:' || 'core'). Result is cached. + * + * @return {Promise} current environment + */ + private async getEnvironment(): Promise { + if (!this.cached.environment) { + this.cached.environment = await getEnvironment(this.options.web3); + } + return this.cached.environment; + } + + /** + * Get web3 contract instance for DID registry contract via ENS. Result is cached. + * + * @return {Promise} DID registry contract + */ + private async getRegistryContract(): Promise { + if (!this.cached.didRegistryContract) { + const didRegistryDomain = this.options.nameResolver.getDomainName( + this.options.nameResolver.config.domains.didRegistry, + ); + const didRegistryAddress = await this.options.nameResolver.getAddress(didRegistryDomain); + this.cached.didRegistryContract = this.options.contractLoader.loadContract( + 'DidRegistry', didRegistryAddress, + ); + } + return this.cached.didRegistryContract; + } + + /** + * Pad leading zeroes to 20B identity, required for addressing 32B identity references in + * registry. + * + * @param {string} identity identity contract/hash to pad + * @return {string} padded identity value + */ + private padIdentity(identity: string): string { + return identity.length !== 66 + ? `0x${identity.replace(/^0x/, '').padStart(64, '0')}` + : identity; + } + + /** + * Method to ensure no public key array types are written into a retrieved did document. This is + * just a legacy method because we still have various faulty DID documents stored that have an + * array as the publicKey.type property. + * + * @param {any} result The cleaned and valid DID document + */ + private async removePublicKeyTypeArray(result: any): Promise { + // TODO: Method can be deleted as soon as there is a real DID validation in place + const cleanedResult = result; + let keyTypes = []; + + for (const pos in result.publicKey) { + // Discard ERC725ManagementKey type entry + if (result.publicKey[pos].type instanceof Array) { + keyTypes = result.publicKey[pos].type.filter((type) => !type.startsWith('ERC725')); + [cleanedResult.publicKey[pos].type] = keyTypes; + } + } + return cleanedResult; + } + + /** + * Validates if a given DID is a valid evan DID and returns its parts. + * + * @param {string} did DID to validate. + * @return {Promise} The parts of the DID if it is valid. + * @throws If the DID is not valid. + */ + private async validateDidAndGetSections(did: string): Promise { + const groups = didRegEx.exec(did); + if (!groups) { + throw new Error(`Given did ("${did}") is no valid evan DID`); + } + return groups; + } +} diff --git a/src/encryption/aes-blob.spec.ts b/src/encryption/aes-blob.spec.ts index 146c0b94..a5d987d6 100644 --- a/src/encryption/aes-blob.spec.ts +++ b/src/encryption/aes-blob.spec.ts @@ -18,13 +18,13 @@ */ import 'mocha'; +import * as fs from 'fs'; import { expect } from 'chai'; +import { promisify } from 'util'; -const fs = require('fs'); -const { promisify } = require('util'); +import { AesBlob } from './aes-blob'; +import { TestUtils } from '../test/test-utils'; -import { AesBlob } from './aes-blob' -import { TestUtils } from '../test/test-utils' let sampleFile; let fileDescription; @@ -33,7 +33,7 @@ let ipfs; const sampleKey = '346c22768f84f3050f5c94cec98349b3c5cbfa0b7315304e13647a49181fd1ef'; let encryptedFile; -describe('Blob Encryption', function() { +describe('Blob Encryption', function test() { this.timeout(300000); before(async () => { @@ -41,37 +41,37 @@ describe('Blob Encryption', function() { fileDescription = { name: 'testfile.spec.jpg', fileType: 'image/jpeg', - file: sampleFile + file: sampleFile, }; fileValidation = { name: 'testfile.spec.jpg', fileType: 'image/jpeg', - file: sampleFile + file: sampleFile, }; ipfs = await TestUtils.getIpfs(); }); it('should be able to be created', () => { - const aes = new AesBlob({dfs: ipfs}); + const aes = new AesBlob({ dfs: ipfs }); expect(aes).not.to.be.undefined; }); it('should be able to generate keys', async () => { - const aes = new AesBlob({dfs: ipfs}); + const aes = new AesBlob({ dfs: ipfs }); const key = await aes.generateKey(); expect(key).not.to.be.undefined; }); it('should be able to encrypt a sample message', async () => { - const aes = new AesBlob({dfs: ipfs}); - encryptedFile = await aes.encrypt(fileDescription, { key: sampleKey, }); + const aes = new AesBlob({ dfs: ipfs }); + encryptedFile = await aes.encrypt(fileDescription, { key: sampleKey }); expect(encryptedFile.toString('hex')).not.to.be.undefined; }); it('should be able to decrypt a sample message', async () => { - const aes = new AesBlob({dfs: ipfs}); - const decrypted = await aes.decrypt(Buffer.from(encryptedFile, 'hex'), { key: sampleKey, }); + const aes = new AesBlob({ dfs: ipfs }); + const decrypted = await aes.decrypt(Buffer.from(encryptedFile, 'hex'), { key: sampleKey }); expect(decrypted).to.deep.equal(fileValidation); }); }); diff --git a/src/encryption/aes-blob.ts b/src/encryption/aes-blob.ts index 80d5f226..643d4567 100644 --- a/src/encryption/aes-blob.ts +++ b/src/encryption/aes-blob.ts @@ -17,8 +17,6 @@ the following URL: https://evan.network/license/ */ -import crypto = require('crypto-browserify'); - import { Cryptor, CryptoInfo, @@ -26,6 +24,7 @@ import { LoggerOptions, } from '@evan.network/dbcp'; +import crypto = require('crypto-browserify'); /** * generate new intiala vector, length is 16 bytes (aes) @@ -50,23 +49,26 @@ export interface AesBlobOptions extends LoggerOptions { * @class AesBlob (name) */ export class AesBlob extends Logger implements Cryptor { - static defaultOptions = { + public static defaultOptions = { keyLength: 256, algorithm: 'aes-blob', }; private readonly encodingUnencrypted = 'utf-8'; + private readonly encodingEncrypted = 'hex'; - options: any; - algorithm: string; - webCryptoAlgo: string; + public options: any; + + public algorithm: string; + + public webCryptoAlgo: string; - constructor(options?: AesBlobOptions) { + public constructor(options?: AesBlobOptions) { super(options); this.algorithm = 'aes-256-cbc'; this.webCryptoAlgo = 'AES-CBC'; - this.options = Object.assign({}, AesBlob.defaultOptions, options || {}); + this.options = { ...AesBlob.defaultOptions, ...options || {} }; } @@ -76,54 +78,43 @@ export class AesBlob extends Logger implements Cryptor { * @param {string} str string to convert * @return {any} converted input */ - stringToArrayBuffer(str): any { + public stringToArrayBuffer(str): any { const len = str.length; const bytes = new Uint8Array(len); - for (let i = 0; i < len; i++) { + for (let i = 0; i < len; i += 1) { bytes[i] = str.charCodeAt(i); } return bytes.buffer; } - getCryptoInfo(originator: string): CryptoInfo { - const ret = Object.assign({ originator, }, this.options); + public getCryptoInfo(originator: string): CryptoInfo { + const ret = { originator, ...this.options }; delete ret.dfs; return ret; } - chunkBuffer(buffer, chunkSize) { - const result = []; - const len = buffer.length; - let i = 0; - while (i < len) { - result.push(buffer.slice(i, i += chunkSize)); - } - return result; - } - - /** * generate key for cryptor/decryption * * @return {any} The iv from key. */ - async generateKey(): Promise { + public async generateKey(): Promise { return new Promise((resolve, reject) => { crypto.randomBytes(this.options.keyLength / 8, (err, buf) => { if (err) { reject(err); } else { - const hexString = buf.toString('hex') + const hexString = buf.toString('hex'); resolve(hexString); } }); - }) + }); } - async decryptBrowser(algorithm, buffer, decryptKey, iv) { - const key = await (global).crypto.subtle.importKey( + public async decryptBrowser(algorithm, buffer, decryptKey, iv) { + const key = await (global as any).crypto.subtle.importKey( 'raw', decryptKey, { @@ -131,21 +122,21 @@ export class AesBlob extends Logger implements Cryptor { length: 256, }, false, - ['decrypt'] + ['decrypt'], ); - const decrypted = await (global).crypto.subtle.decrypt( + const decrypted = await (global as any).crypto.subtle.decrypt( { name: algorithm, - iv: iv, + iv, }, key, - buffer + buffer, ); return Buffer.from(decrypted); } - async encryptBrowser(algorithm, buffer, encryptionKey, iv) { - const key = await (global).crypto.subtle.importKey( + public async encryptBrowser(algorithm, buffer, encryptionKey, iv) { + const key = await (global as any).crypto.subtle.importKey( 'raw', encryptionKey, { @@ -153,15 +144,15 @@ export class AesBlob extends Logger implements Cryptor { length: 256, }, false, - ['encrypt'] + ['encrypt'], ); - const encrypted = await (global).crypto.subtle.encrypt( + const encrypted = await (global as any).crypto.subtle.encrypt( { name: algorithm, - iv: iv, + iv, }, key, - buffer + buffer, ); return Buffer.from(encrypted); } @@ -173,7 +164,7 @@ export class AesBlob extends Logger implements Cryptor { * @param {any} options cryptor options * @return {Buffer} encrypted message */ - async encrypt(message: any, options: any): Promise { + public async encrypt(message: any, options: any): Promise { try { if (!options.key) { throw new Error('no key given'); @@ -185,18 +176,18 @@ export class AesBlob extends Logger implements Cryptor { const cipher = crypto.createCipheriv( this.algorithm, Buffer.from(options.key, 'hex'), - initialVector + initialVector, ); if (Array.isArray(message)) { const files = []; - for (let blob of message) { + for (const blob of message) { let encrypted; - if ((global).crypto && (global).crypto.subtle) { + if ((global as any).crypto && (global as any).crypto.subtle) { encrypted = await this.encryptBrowser( this.webCryptoAlgo, Buffer.from(blob.file), Buffer.from(options.key, 'hex'), - initialVector + initialVector, ); } else { encrypted = Buffer.concat([cipher.update(Buffer.from(blob.file)), cipher.final()]); @@ -205,21 +196,22 @@ export class AesBlob extends Logger implements Cryptor { const stateMd5 = crypto.createHash('md5').update(encryptedWithIv).digest('hex'); files.push({ path: stateMd5, - content: encryptedWithIv + content: encryptedWithIv, }); } const hashes = await this.options.dfs.addMultiple(files); - for (let i = 0; i < message.length; i++) { + for (let i = 0; i < message.length; i += 1) { + // eslint-disable-next-line no-param-reassign message[i].file = hashes[i]; } } else { let encrypted; - if ((global).crypto && (global).crypto.subtle) { + if ((global as any).crypto && (global as any).crypto.subtle) { encrypted = await this.encryptBrowser( this.webCryptoAlgo, Buffer.from(message.file), Buffer.from(options.key, 'hex'), - initialVector + initialVector, ); } else { encrypted = Buffer.concat([cipher.update(Buffer.from(message.file)), cipher.final()]); @@ -227,25 +219,26 @@ export class AesBlob extends Logger implements Cryptor { const encryptedWithIv = Buffer.concat([initialVector, encrypted]); const stateMd5 = crypto.createHash('md5').update(encryptedWithIv).digest('hex'); const hash = await this.options.dfs.add(stateMd5, encryptedWithIv); + // eslint-disable-next-line no-param-reassign message.file = hash; } const wrapperMessage = Buffer.from(JSON.stringify(message), this.encodingUnencrypted); - if ((global).crypto && (global).crypto.subtle) { + if ((global as any).crypto && (global as any).crypto.subtle) { encryptedWrapperMessage = await this.encryptBrowser( this.webCryptoAlgo, Buffer.from(wrapperMessage), Buffer.from(options.key, 'hex'), - initialVector + initialVector, ); } else { const wrapperDecipher = crypto.createCipheriv( this.algorithm, Buffer.from(options.key, 'hex'), - initialVector + initialVector, ); encryptedWrapperMessage = Buffer.concat([ wrapperDecipher.update(wrapperMessage), - wrapperDecipher.final() + wrapperDecipher.final(), ]); } return Promise.resolve(Buffer.concat([initialVector, encryptedWrapperMessage])); @@ -262,7 +255,7 @@ export class AesBlob extends Logger implements Cryptor { * @param {any} options decryption options * @return {any} decrypted message */ - async decrypt(message: Buffer, options: any): Promise { + public async decrypt(message: Buffer, options: any): Promise { try { if (!options.key) { throw new Error('no key given'); @@ -271,7 +264,7 @@ export class AesBlob extends Logger implements Cryptor { const initialVector = message.slice(0, 16); const encrypted = message.slice(16); let decrypted; - if ((global).crypto && (global).crypto.subtle) { + if ((global as any).crypto && (global as any).crypto.subtle) { decrypted = await this.decryptBrowser(this.webCryptoAlgo, encrypted, Buffer.from(options.key, 'hex'), initialVector); } else { const decipher = crypto.createDecipheriv(this.algorithm, Buffer.from(options.key, 'hex'), initialVector); @@ -282,20 +275,16 @@ export class AesBlob extends Logger implements Cryptor { if (Array.isArray(wrapper)) { - const encryptedFiles = []; - for (let blob of wrapper) { + for (const blob of wrapper) { const ipfsFile = await this.options.dfs.get(blob.file, true); let file = Buffer.from(''); const initialVectorFile = ipfsFile.slice(0, 16); const encryptedFile = ipfsFile.slice(16); - if ((global).crypto && (global).crypto.subtle) { + if ((global as any).crypto && (global as any).crypto.subtle) { file = await this.decryptBrowser(this.webCryptoAlgo, encryptedFile, Buffer.from(options.key, 'hex'), initialVectorFile); } else { const fileDecipher = crypto.createDecipheriv(this.algorithm, Buffer.from(options.key, 'hex'), initialVectorFile); - const chunks = this.chunkBuffer(encryptedFile, 1024); - for (let chunk of chunks) { - file = Buffer.concat([file, fileDecipher.update(chunk)]); - } + file = Buffer.concat([file, fileDecipher.update(encryptedFile)]); file = Buffer.concat([file, fileDecipher.final()]); } blob.file = file; @@ -305,14 +294,11 @@ export class AesBlob extends Logger implements Cryptor { const initialVectorFile = ipfsFile.slice(0, 16); const encryptedFile = ipfsFile.slice(16); let file = Buffer.from(''); - if ((global).crypto && (global).crypto.subtle) { + if ((global as any).crypto && (global as any).crypto.subtle) { file = await this.decryptBrowser(this.webCryptoAlgo, encryptedFile, Buffer.from(options.key, 'hex'), initialVectorFile); } else { const fileDecipher = crypto.createDecipheriv(this.algorithm, Buffer.from(options.key, 'hex'), initialVectorFile); - const chunks = this.chunkBuffer(encryptedFile, 1024); - for (let chunk of chunks) { - file = Buffer.concat([file, fileDecipher.update(chunk)]); - } + file = Buffer.concat([file, fileDecipher.update(encryptedFile)]); file = Buffer.concat([file, fileDecipher.final()]); } wrapper.file = file; diff --git a/src/encryption/aes-ecb.spec.ts b/src/encryption/aes-ecb.spec.ts index af147053..ec712425 100644 --- a/src/encryption/aes-ecb.spec.ts +++ b/src/encryption/aes-ecb.spec.ts @@ -21,13 +21,10 @@ import 'mocha'; import { expect } from 'chai'; import { randomBytes } from 'crypto'; -import { AesEcb } from './aes-ecb' -import { TestUtils } from '../test/test-utils' +import { AesEcb } from './aes-ecb'; -const sampleUnencrypted = 'Id commodo nulla ut eiusmod.'; -const sampleKey = '346c22768f84f3050f5c94cec98349b3c5cbfa0b7315304e13647a49181fd1ef'; -describe('aes (ecb) handler', function() { +describe('aes (ecb) handler', function test() { this.timeout(300000); it('should be able to be created', () => { @@ -45,8 +42,8 @@ describe('aes (ecb) handler', function() { const aes = new AesEcb(); const key = await aes.generateKey(); const message = randomBytes(32); - const encrypted = await aes.encrypt(message, { key: key, }); - const decrypted = await aes.decrypt(encrypted, { key: key, }); + const encrypted = await aes.encrypt(message, { key }); + const decrypted = await aes.decrypt(encrypted, { key }); expect(decrypted.toString('utf-8')).to.eq(message.toString('utf-8')); }); }); diff --git a/src/encryption/aes-ecb.ts b/src/encryption/aes-ecb.ts index 90a784a8..08e56405 100644 --- a/src/encryption/aes-ecb.ts +++ b/src/encryption/aes-ecb.ts @@ -17,36 +17,37 @@ the following URL: https://evan.network/license/ */ -import crypto = require('crypto-browserify'); - import { Cryptor, CryptoInfo, Logger, - LoggerOptions, + LoggerOptions as AesEcbOptions, } from '@evan.network/dbcp'; +import crypto = require('crypto-browserify'); + /** * aes ecb instance options */ -export interface AesEcbOptions extends LoggerOptions { - -} +export { + LoggerOptions as AesEcbOptions, +} from '@evan.network/dbcp'; export class AesEcb extends Logger implements Cryptor { - static defaultOptions = { + public static defaultOptions = { keyLength: 256, algorithm: 'aes-256-ecb', }; + public options: any; + private readonly encodingUnencrypted = 'utf-8'; - private readonly encodingEncrypted = 'hex'; - options: any; + private readonly encodingEncrypted = 'hex'; - constructor(options?: AesEcbOptions) { + public constructor(options?: AesEcbOptions) { super(options); - this.options = Object.assign({}, AesEcb.defaultOptions, options || {}); + this.options = { ...AesEcb.defaultOptions, ...options || {} }; } @@ -56,18 +57,18 @@ export class AesEcb extends Logger implements Cryptor { * @param {string} str string to convert * @return {Buffer} converted input */ - stringToArrayBuffer(str) { - let len = str.length; - let bytes = new Uint8Array( len ); - for (let i = 0; i < len; i++) { + public stringToArrayBuffer(str) { + const len = str.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i += 1) { bytes[i] = str.charCodeAt(i); } return bytes.buffer; } - getCryptoInfo(originator: string): CryptoInfo { - return Object.assign({ originator, }, this.options); + public getCryptoInfo(originator: string): CryptoInfo { + return { originator, ...this.options }; } /** @@ -76,17 +77,17 @@ export class AesEcb extends Logger implements Cryptor { * @return {any} The iv from key. */ - async generateKey(): Promise { + public async generateKey(): Promise { return new Promise((resolve, reject) => { crypto.randomBytes(this.options.keyLength / 8, (err, buf) => { if (err) { reject(err); } else { - const hexString = buf.toString('hex') + const hexString = buf.toString('hex'); resolve(hexString); } }); - }) + }); } /** @@ -96,7 +97,7 @@ export class AesEcb extends Logger implements Cryptor { * @param {any} options cryptor options * @return {Buffer} encrypted message */ - async encrypt(message: Buffer, options: any): Promise { + public async encrypt(message: Buffer, options: any): Promise { try { if (!options.key) { throw new Error('no key given'); @@ -105,7 +106,7 @@ export class AesEcb extends Logger implements Cryptor { const cipher = crypto.createCipheriv( this.options.algorithm, Buffer.from(computedKey, 'hex'), - '' + '', ); cipher.setAutoPadding(false); const encrypted = Buffer.concat([cipher.update(message), cipher.final()]); @@ -123,7 +124,7 @@ export class AesEcb extends Logger implements Cryptor { * @param {any} options decryption options * @return {any} decrypted message */ - async decrypt(message: Buffer, options: any): Promise { + public async decrypt(message: Buffer, options: any): Promise { try { if (!options.key) { throw new Error('no key given'); @@ -132,7 +133,7 @@ export class AesEcb extends Logger implements Cryptor { const decipher = crypto.createDecipheriv( this.options.algorithm, Buffer.from(computedKey, 'hex'), - '' + '', ); decipher.setAutoPadding(false); const decrypted = Buffer.concat([decipher.update(message), decipher.final()]); @@ -153,16 +154,20 @@ export class AesEcb extends Logger implements Cryptor { private computeSecret(passphrase: Buffer): string { let nkey = 32; let niv = 0; - for (let key = '', iv = '', p = '';;) { + for (let key = '', p = ''; ;) { const h = crypto.createHash('md5'); h.update(p, 'hex'); h.update(passphrase); p = h.digest('hex'); - let n, i = 0; + let n; + let i = 0; n = Math.min(p.length - i, 2 * nkey); - nkey -= n / 2, key += p.slice(i, i + n), i += n; + nkey -= n / 2; + key += p.slice(i, i + n); + i += n; n = Math.min(p.length - i, 2 * niv); - niv -= n / 2, iv += p.slice(i, i + n), i += n; + niv -= n / 2; + i += n; if (nkey + niv === 0) { return key; } diff --git a/src/encryption/aes.spec.ts b/src/encryption/aes.spec.ts index 2ba41a99..38264806 100644 --- a/src/encryption/aes.spec.ts +++ b/src/encryption/aes.spec.ts @@ -20,11 +20,10 @@ import 'mocha'; import { expect } from 'chai'; -import { Aes } from './aes' -import { TestUtils } from '../test/test-utils' +import { Aes } from './aes'; -describe('aes handler', function() { +describe('aes handler', function test() { this.timeout(300000); it('should be able to be created', () => { @@ -42,8 +41,8 @@ describe('aes handler', function() { const aes = new Aes(); const key = await aes.generateKey(); const message = Math.random().toString(); - const encrypted = await aes.encrypt(message, { key: key, }); - const decrypted = await aes.decrypt(encrypted, { key: key, }); + const encrypted = await aes.encrypt(message, { key }); + const decrypted = await aes.decrypt(encrypted, { key }); expect(decrypted.toString('utf-8')).to.eq(message); }); @@ -51,8 +50,8 @@ describe('aes handler', function() { const aes = new Aes(); const key = await aes.generateKey(); const message = Math.random(); - const encrypted = await aes.encrypt(message, { key: key, }); - const decrypted = await aes.decrypt(encrypted, { key: key, }); + const encrypted = await aes.encrypt(message, { key }); + const decrypted = await aes.decrypt(encrypted, { key }); expect(decrypted).to.eq(message); }); }); diff --git a/src/encryption/aes.ts b/src/encryption/aes.ts index 0ff1cce5..a10cc282 100644 --- a/src/encryption/aes.ts +++ b/src/encryption/aes.ts @@ -17,13 +17,20 @@ the following URL: https://evan.network/license/ */ -import crypto = require('crypto-browserify'); - import { Cryptor, CryptoInfo, Logger, - LoggerOptions, + LoggerOptions as AesOptions, +} from '@evan.network/dbcp'; + +import crypto = require('crypto-browserify'); + +/** + * aes instance options + */ +export { + LoggerOptions as AesOptions, } from '@evan.network/dbcp'; @@ -36,27 +43,21 @@ function generateInitialVector(): Buffer { return crypto.randomBytes(16); } -/** - * aes instance options - */ -export interface AesOptions extends LoggerOptions { - -} - export class Aes extends Logger implements Cryptor { - static defaultOptions = { + public static defaultOptions = { keyLength: 256, algorithm: 'aes-256-cbc', }; + public options: any; + private readonly encodingUnencrypted = 'utf-8'; - private readonly encodingEncrypted = 'hex'; - options: any; + private readonly encodingEncrypted = 'hex'; - constructor(options?: AesOptions) { + public constructor(options?: AesOptions) { super(options); - this.options = Object.assign({}, Aes.defaultOptions, options || {}); + this.options = { ...Aes.defaultOptions, ...options || {} }; } @@ -66,18 +67,18 @@ export class Aes extends Logger implements Cryptor { * @param {string} str string to convert * @return {Buffer} converted input */ - stringToArrayBuffer(str) { - let len = str.length; - let bytes = new Uint8Array( len ); - for (let i = 0; i < len; i++) { + public stringToArrayBuffer(str) { + const len = str.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i += 1) { bytes[i] = str.charCodeAt(i); } return bytes.buffer; } - getCryptoInfo(originator: string): CryptoInfo { - return Object.assign({ originator, }, this.options); + public getCryptoInfo(originator: string): CryptoInfo { + return { originator, ...this.options }; } /** @@ -86,17 +87,17 @@ export class Aes extends Logger implements Cryptor { * @return {any} The iv from key. */ - async generateKey(): Promise { + public async generateKey(): Promise { return new Promise((resolve, reject) => { crypto.randomBytes(this.options.keyLength / 8, (err, buf) => { if (err) { reject(err); } else { - const hexString = buf.toString('hex') + const hexString = buf.toString('hex'); resolve(hexString); } }); - }) + }); } /** @@ -106,7 +107,7 @@ export class Aes extends Logger implements Cryptor { * @param {any} options cryptor options * @return {Buffer} encrypted message */ - async encrypt(message: any, options: any): Promise { + public async encrypt(message: any, options: any): Promise { try { if (!options.key) { throw new Error('no key given'); @@ -130,7 +131,7 @@ export class Aes extends Logger implements Cryptor { * @param {any} options decryption options * @return {any} decrypted message */ - async decrypt(message: Buffer, options: any): Promise { + public async decrypt(message: Buffer, options: any): Promise { try { if (!options.key) { throw new Error('no key given'); diff --git a/src/encryption/crypto-provider.ts b/src/encryption/crypto-provider.ts index 08da3526..bf5e2dac 100644 --- a/src/encryption/crypto-provider.ts +++ b/src/encryption/crypto-provider.ts @@ -17,11 +17,6 @@ the following URL: https://evan.network/license/ */ -import { - CryptoInfo, - Cryptor, -} from '@evan.network/dbcp'; - import * as Dbcp from '@evan.network/dbcp'; @@ -30,8 +25,9 @@ import * as Dbcp from '@evan.network/dbcp'; * * @class CryptoProvider (name) */ +// eslint-disable-next-line import/prefer-default-export export class CryptoProvider extends Dbcp.CryptoProvider { - constructor(cryptors) { + public constructor(cryptors) { super(cryptors); } @@ -41,7 +37,7 @@ export class CryptoProvider extends Dbcp.CryptoProvider { * @param {CryptoInfo} info details about en-/decryption * @return {Cryptor} matching cryptor */ - getCryptorByCryptoInfo(info: CryptoInfo): Cryptor { + public getCryptorByCryptoInfo(info: Dbcp.CryptoInfo): Dbcp.Cryptor { switch (info.algorithm) { case 'aes-256-cbc': return this.cryptors.aes; case 'unencrypted': return this.cryptors.unencrypted; diff --git a/src/encryption/encryption-wrapper.spec.ts b/src/encryption/encryption-wrapper.spec.ts index 441551fd..8c3bb7e1 100644 --- a/src/encryption/encryption-wrapper.spec.ts +++ b/src/encryption/encryption-wrapper.spec.ts @@ -18,16 +18,13 @@ */ import 'mocha'; +import { Executor } from '@evan.network/dbcp'; import { expect } from 'chai'; import { promisify } from 'util'; import { readFile } from 'fs'; -import { - Envelope, - Executor, -} from '@evan.network/dbcp'; import { accounts } from '../test/accounts'; -import { CryptoProvider } from '../encryption/crypto-provider'; +import { CryptoProvider } from './crypto-provider'; import { Sharing } from '../contracts/sharing'; import { TestUtils } from '../test/test-utils'; import { @@ -37,26 +34,27 @@ import { } from './encryption-wrapper'; -describe('Encryption Wrapper', function() { +describe('Encryption Wrapper', function test() { this.timeout(300000); let cryptoProvider: CryptoProvider; let encryptionWrapper: EncryptionWrapper; let executor: Executor; let sharing0: Sharing; - let sharing1: Sharing; before(async () => { + const web3 = TestUtils.getWeb3(); // data sharing sha3 self key and edges to self and other accounts const sha3 = (...args) => web3.utils.soliditySha3(...args); const sha9 = (accountId1, accountId2) => sha3(...[sha3(accountId1), sha3(accountId2)].sort()); - const getKeys = (ownAccount, partnerAccount) => - [sha3(ownAccount), ...[ownAccount, partnerAccount].map(partner => sha9(ownAccount, partner))]; - const web3 = TestUtils.getWeb3(); + const getKeys = ( + ownAccount, partnerAccount, + ) => [sha3(ownAccount), ...[ownAccount, partnerAccount].map( + (partner) => sha9(ownAccount, partner), + )]; const dfs = await TestUtils.getIpfs(); cryptoProvider = TestUtils.getCryptoProvider(dfs); executor = await TestUtils.getExecutor(web3); sharing0 = await TestUtils.getSharing(web3, dfs, getKeys(accounts[0], accounts[1])); - sharing1 = await TestUtils.getSharing(web3, dfs, getKeys(accounts[1], accounts[0])); encryptionWrapper = new EncryptionWrapper({ cryptoProvider, nameResolver: await TestUtils.getNameResolver(web3), @@ -66,9 +64,6 @@ describe('Encryption Wrapper', function() { }); }); - after(async () => { - }); - it('should be able to be created', async () => { const testInstance = new EncryptionWrapper(); expect(testInstance).not.to.be.undefined; @@ -77,7 +72,8 @@ describe('Encryption Wrapper', function() { describe('when using keys stored in profile', () => { it('should be able to encrypt and decrypt files with a new key from profile', async () => { const file = await promisify(readFile)( - `${__dirname}/testfile.spec.jpg`); + `${__dirname}/testfile.spec.jpg`, + ); const sampleFile = [{ name: 'testfile.spec.jpg', fileType: 'image/jpeg', @@ -138,18 +134,19 @@ describe('Encryption Wrapper', function() { describe('when using keys stored in Multisharings', () => { let multiSharingAddress: string; - let sharingId = TestUtils.getRandomBytes32(); + const sharingId = TestUtils.getRandomBytes32(); before(async () => { - const randomSecret = `super secret; ${Math.random()}`; const contract = await executor.createContract( - 'MultiShared', [], { from: accounts[0], gas: 500000, }); + 'MultiShared', [], { from: accounts[0], gas: 500000 }, + ); multiSharingAddress = contract.options.address; }); it('should be able to encrypt and decrypt files with a new key from profile', async () => { const file = await promisify(readFile)( - `${__dirname}/testfile.spec.jpg`); + `${__dirname}/testfile.spec.jpg`, + ); const sampleFile = [{ name: 'testfile.spec.jpg', fileType: 'image/jpeg', @@ -167,7 +164,7 @@ describe('Encryption Wrapper', function() { keyContext, EncryptionWrapperKeyType.Sharing, EncryptionWrapperCryptorType.File, - { sharingContractId: multiSharingAddress, sharingId } + { sharingContractId: multiSharingAddress, sharingId }, ); // generate and store new key for crypto info @@ -180,7 +177,8 @@ describe('Encryption Wrapper', function() { // encrypt files (key is pulled from profile) const encrypted = await encryptionWrapper.encrypt( - sampleFile, cryptoInfo, encryptionArtifacts); + sampleFile, cryptoInfo, encryptionArtifacts, + ); expect(encrypted).to.haveOwnProperty('cryptoInfo'); expect(encrypted).to.haveOwnProperty('private'); @@ -200,7 +198,7 @@ describe('Encryption Wrapper', function() { keyContext, EncryptionWrapperKeyType.Sharing, EncryptionWrapperCryptorType.Content, - { sharingContractId: multiSharingAddress, sharingId } + { sharingContractId: multiSharingAddress, sharingId }, ); // generate and store new key for crypto info @@ -213,7 +211,8 @@ describe('Encryption Wrapper', function() { // encrypt files (key is pulled from profile) const encrypted = await encryptionWrapper.encrypt( - sampleData, cryptoInfo, encryptionArtifacts); + sampleData, cryptoInfo, encryptionArtifacts, + ); expect(encrypted).to.haveOwnProperty('cryptoInfo'); expect(encrypted).to.haveOwnProperty('private'); @@ -226,15 +225,16 @@ describe('Encryption Wrapper', function() { let sharingAddress: string; before(async () => { - const randomSecret = `super secret; ${Math.random()}`; const contract = await executor.createContract( - 'Shared', [], { from: accounts[0], gas: 500000, }); + 'Shared', [], { from: accounts[0], gas: 500000 }, + ); sharingAddress = contract.options.address; }); it('should be able to encrypt and decrypt files with a new key from profile', async () => { const file = await promisify(readFile)( - `${__dirname}/testfile.spec.jpg`); + `${__dirname}/testfile.spec.jpg`, + ); const sampleFile = [{ name: 'testfile.spec.jpg', fileType: 'image/jpeg', @@ -252,7 +252,7 @@ describe('Encryption Wrapper', function() { keyContext, EncryptionWrapperKeyType.Sharing, EncryptionWrapperCryptorType.File, - { sharingContractId: sharingAddress } + { sharingContractId: sharingAddress }, ); // generate and store new key for crypto info @@ -265,7 +265,8 @@ describe('Encryption Wrapper', function() { // encrypt files (key is pulled from profile) const encrypted = await encryptionWrapper.encrypt( - sampleFile, cryptoInfo, encryptionArtifacts); + sampleFile, cryptoInfo, encryptionArtifacts, + ); expect(encrypted).to.haveOwnProperty('cryptoInfo'); expect(encrypted).to.haveOwnProperty('private'); @@ -285,7 +286,7 @@ describe('Encryption Wrapper', function() { keyContext, EncryptionWrapperKeyType.Sharing, EncryptionWrapperCryptorType.Content, - { sharingContractId: sharingAddress } + { sharingContractId: sharingAddress }, ); // generate and store new key for crypto info @@ -298,7 +299,8 @@ describe('Encryption Wrapper', function() { // encrypt files (key is pulled from profile) const encrypted = await encryptionWrapper.encrypt( - sampleData, cryptoInfo, encryptionArtifacts); + sampleData, cryptoInfo, encryptionArtifacts, + ); expect(encrypted).to.haveOwnProperty('cryptoInfo'); expect(encrypted).to.haveOwnProperty('private'); @@ -310,7 +312,8 @@ describe('Encryption Wrapper', function() { describe('when using keys stored separately', () => { it('should be able to encrypt and decrypt files with a new key from profile', async () => { const file = await promisify(readFile)( - `${__dirname}/testfile.spec.jpg`); + `${__dirname}/testfile.spec.jpg`, + ); const sampleFile = [{ name: 'testfile.spec.jpg', fileType: 'image/jpeg', diff --git a/src/encryption/encryption-wrapper.ts b/src/encryption/encryption-wrapper.ts index caccdd66..7789976a 100644 --- a/src/encryption/encryption-wrapper.ts +++ b/src/encryption/encryption-wrapper.ts @@ -49,8 +49,10 @@ export enum EncryptionWrapperKeyType { /** custom key handling means that the key is handled elsewhere and has to be given to profile */ Custom = 'custom', /** key is stored in profile, usually in property "encryptionKeys" */ + // eslint-disable-next-line no-shadow Profile = 'profile', /** key is stored in Shared or MultiShared contract */ + // eslint-disable-next-line no-shadow Sharing = 'sharing', } @@ -73,17 +75,18 @@ export interface EncryptionWrapperOptions extends LoggerOptions { * @class EncryptionWrapper (name) */ export class EncryptionWrapper extends Logger { - static defaultOptions = { + public static defaultOptions = { keyLength: 256, algorithm: 'aes-blob', }; + public options: EncryptionWrapperOptions; + private readonly encodingUnencrypted = 'utf-8'; - private readonly encodingEncrypted = 'hex'; - options: EncryptionWrapperOptions; + private readonly encodingEncrypted = 'hex'; - constructor(options?: EncryptionWrapperOptions) { + public constructor(options?: EncryptionWrapperOptions) { super(options); this.options = { ...options }; } @@ -96,19 +99,17 @@ export class EncryptionWrapper extends Logger { */ public async decrypt( toDecrypt: Envelope, - artifacts?: - // for type === Sharing - { accountId: string, propertyName: string } | - // for type === Custom - { key: string } + // for type === Sharing or type === Custom + artifacts?: { accountId: string; propertyName: string } | { key: string }, ): Promise { - const [ cryptor, key ] = await Promise.all([ + const [cryptor, key] = await Promise.all([ this.options.cryptoProvider.getCryptorByCryptoInfo(toDecrypt.cryptoInfo), this.getKey(toDecrypt.cryptoInfo, artifacts), ]); const decrypted = await cryptor.decrypt( - Buffer.from(toDecrypt.private, this.encodingEncrypted), { key }); + Buffer.from(toDecrypt.private, this.encodingEncrypted), { key }, + ); return decrypted; } @@ -124,13 +125,10 @@ export class EncryptionWrapper extends Logger { public async encrypt( toEncrypt: any, cryptoInfo: CryptoInfo, - artifacts?: - // for type === Sharing - { accountId: string, block?: number, propertyName: string } | - // for type === Custom - { key: string } + // for type === Sharing or type === Custom + artifacts?: { accountId: string; block?: number; propertyName: string } | { key: string }, ): Promise { - const [ cryptor, key ] = await Promise.all([ + const [cryptor, key] = await Promise.all([ this.options.cryptoProvider.getCryptorByCryptoInfo(cryptoInfo), this.getKey(cryptoInfo, artifacts), ]); @@ -177,9 +175,8 @@ export class EncryptionWrapper extends Logger { keyContext: string, keyType: EncryptionWrapperKeyType, cryptorType: EncryptionWrapperCryptorType = EncryptionWrapperCryptorType.Content, - artifacts?: - // for type === Sharing - { sharingContractId: string, sharingId?: string } + // for type === Sharing + artifacts?: { sharingContractId: string; sharingId?: string }, ): Promise { switch (keyType) { case EncryptionWrapperKeyType.Custom: @@ -189,17 +186,18 @@ export class EncryptionWrapper extends Logger { block: await this.options.web3.eth.getBlockNumber(), originator: `${keyType}:${keyContext}`, }; - case EncryptionWrapperKeyType.Sharing: + case EncryptionWrapperKeyType.Sharing: { this.checkProperties(artifacts, ['sharingContractId']); const { sharingContractId, sharingId } = artifacts; - const originator = typeof sharingId !== 'undefined' ? - `${keyType}:${sharingContractId}:${sharingId}` : - `${keyType}:${sharingContractId}`; + const originator = typeof sharingId !== 'undefined' + ? `${keyType}:${sharingContractId}:${sharingId}` + : `${keyType}:${sharingContractId}`; return { algorithm: cryptorType, block: await this.options.web3.eth.getBlockNumber(), originator, }; + } default: throw new Error(`unknown key type "${keyType}"`); } @@ -213,11 +211,8 @@ export class EncryptionWrapper extends Logger { */ public async getKey( cryptoInfo: CryptoInfo, - artifacts?: - // for type === Sharing - { accountId: string, propertyName: string } | - // for type === Custom - { key: string } + // for type === Sharing or type === Custom + artifacts?: { accountId: string; propertyName: string } | { key: string }, ) { let result; const split = cryptoInfo.originator.split(':'); @@ -228,9 +223,9 @@ export class EncryptionWrapper extends Logger { case 'profile': result = await this.options.profile.getEncryptionKey(split[1]); break; - case 'sharing': + case 'sharing': { this.checkProperties(artifacts, ['accountId']); - const [ contractid, sharingId = null ] = split.slice(1); + const [contractid, sharingId = null] = split.slice(1); const { accountId, propertyName = '*' } = artifacts as any; result = await this.options.sharing.getKey( contractid, @@ -240,11 +235,13 @@ export class EncryptionWrapper extends Logger { sharingId, ); break; - case 'custom': + } + case 'custom': { this.checkProperties(artifacts, ['key']); const { key } = artifacts as any; result = key; break; + } default: throw new Error(`unknown key type "${split[0]}"`); } @@ -269,9 +266,8 @@ export class EncryptionWrapper extends Logger { public async storeKey( cryptoInfo: CryptoInfo, key: any, - artifacts?: - // for type === Sharing - { accountId: string, receiver?: string } + // for type === Sharing + artifacts?: { accountId: string; receiver?: string }, ): Promise { const split = cryptoInfo.originator.split(':'); if (split.length < 2) { @@ -287,7 +283,7 @@ export class EncryptionWrapper extends Logger { } case 'sharing': { this.checkProperties(artifacts, ['accountId']); - const [ contractid, sharingId = null ] = split.slice(1); + const [contractid, sharingId = null] = split.slice(1); await this.options.sharing.addSharing( contractid, artifacts.accountId, @@ -314,13 +310,15 @@ export class EncryptionWrapper extends Logger { */ private checkProperties(artifacts: any, properties: string[]) { if (artifacts) { - const missing = properties.filter(property => !artifacts.hasOwnProperty(property)); + const missing = properties.filter( + (property) => !Object.prototype.hasOwnProperty.call(artifacts, property), + ); if (missing.length) { throw new Error([ 'artifacts is missing ', missing.length === 1 ? 'property: ' : 'properties: ', - missing.join(', ') - ].join('')) + missing.join(', '), + ].join('')); } } } diff --git a/src/index.ts b/src/index.ts index f05027e1..e30aebc4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,71 +17,77 @@ the following URL: https://evan.network/license/ */ +// expose dbcp modules +// partial and custom exports +import { getSmartAgentAuthHeaders } from './common/utils'; + +/** ****** export these libraries to be able to build the blockchain-core into a umd bundle ***** */ +import * as AccountType from './profile/types/types'; + export { AccountStore, + AccountStoreOptions, ContractLoader, + ContractLoaderOptions, + CryptoInfo, + Cryptor, DfsInterface, Envelope, EventHub, + EventHubOptions, Executor, + ExecutorOptions, KeyProvider, KeyProviderInterface, - Logger, + KeyProviderOptions, + KeyStoreInterface, LogLevel, + LogLogInterface, + Logger, + LoggerOptions, SignerExternal, + SignerInterface, SignerInternal, + SignerInternalOptions, Unencrypted, Validator, -} from '@evan.network/dbcp' + ValidatorOptions, +} from '@evan.network/dbcp'; -export { Aes } from './encryption/aes'; -export { AesBlob } from './encryption/aes-blob'; -export { AesEcb } from './encryption/aes-ecb'; -export { BaseContract, ContractState } from './contracts/base-contract/base-contract'; -export { BusinessCenterProfile } from './profile/business-center-profile'; -export { createDefaultRuntime, Runtime } from './runtime'; -export { CryptoProvider } from './encryption/crypto-provider'; -export { DataContract } from './contracts/data-contract/data-contract'; -export { Description } from './shared-description'; -export { EncryptionWrapperCryptorType, EncryptionWrapperKeyType, EncryptionWrapperOptions, EncryptionWrapper } from './encryption/encryption-wrapper'; -export { ExecutorAgent } from './contracts/executor-agent'; -export { ExecutorWallet } from './contracts/executor-wallet'; -export { Ipfs } from './dfs/ipfs'; -export { Ipld } from './dfs/ipld'; -export { KeyExchange } from './keyExchange'; -export { Mailbox, Mail } from './mailbox'; -export { NameResolver } from './name-resolver'; -export { Onboarding } from './onboarding'; -export { Payments } from './payments'; -export { Profile } from './profile/profile'; -export { RightsAndRoles, ModificationType, PropertyType } from './contracts/rights-and-roles'; -export { ServiceContract, Answer, AnswerResult, Call, CallResult } from './contracts/service-contract/service-contract'; -export { Sharing } from './contracts/sharing'; -export { - Verifications, - VerificationsDelegationInfo, - VerificationsQueryOptions, - VerificationsResultV2, - VerificationsStatus, - VerificationsStatusComputer, - VerificationsStatusFlagsV2, - VerificationsStatusV2, - VerificationsValidationOptions, - VerificationsVerificationEntry, - VerificationsVerificationEntryStatusComputer, -} from './verifications/verifications'; -export { Votings, MemberOptions, ProposalInfo, ProposalInfos, ProposalOptions, VotingsContractOptions } from './votings/votings'; -export { Wallet } from './contracts/wallet'; -export { Container, ContainerConfig, ContainerFile, ContainerTemplate, ContainerPlugin, ContainerTemplateProperty, - ContainerShareConfig, ContainerUnshareConfig, ContainerOptions, } from './contracts/digital-twin/container'; -export { DigitalTwin, DigitalTwinEntryType, DigitalTwinConfig, DigitalTwinIndexEntry, - DigitalTwinVerificationEntry, } from './contracts/digital-twin/digital-twin'; -import { getSmartAgentAuthHeaders } from './common/utils'; +// expose modules from here +export * from './contracts/base-contract/base-contract'; +export * from './contracts/data-contract/data-contract'; +export * from './contracts/digital-twin/container'; +export * from './contracts/digital-twin/digital-twin'; +export * from './contracts/executor-agent'; +export * from './contracts/executor-wallet'; +export * from './contracts/rights-and-roles'; +export * from './contracts/service-contract/service-contract'; +export * from './contracts/sharing'; +export * from './contracts/signer-identity'; +export * from './contracts/wallet'; +export * from './dfs/ipfs'; +export * from './dfs/ipld'; +export * from './did/did'; +export * from './encryption/aes'; +export * from './encryption/aes-blob'; +export * from './encryption/aes-ecb'; +export * from './encryption/crypto-provider'; +export * from './encryption/encryption-wrapper'; +export * from './keyExchange'; +export * from './mailbox'; +export * from './name-resolver'; +export * from './onboarding'; +export * from './payments'; +export * from './profile/business-center-profile'; +export * from './profile/profile'; +export * from './runtime'; +export * from './shared-description'; +export * from './vc/vc'; +export * from './verifications/verifications'; +export * from './votings/votings'; const utils = { getSmartAgentAuthHeaders }; export { utils }; - -/******** export these libraries to be able to build the blockchain-core into an umd bundle ********/ -import * as AccountType from './profile/types/types'; import Web3 = require('web3'); import crypto = require('crypto'); import keystore = require('../libs/eth-lightwallet/keystore.js'); @@ -89,14 +95,15 @@ import lodash = require('lodash'); import prottle = require('prottle'); // assign to export Buffer; const buffer = Buffer; -// load adjusted bitcore mnemonic lib and do not load the full API specification to reduce bundle size +// load adjusted bitcore mnemonic lib and do not load the full API specification to reduce +// bundle size // be careful when used adjusted components! import Mnemonic = require('../libs/bitcore-mnemonic/mnemonic.js'); -let instanceId = Date.now() + Math.random(); +const instanceId = Date.now() + Math.random(); // used for global & shared available logLog -let logLog = [ ]; +const logLog = []; // push everything into the logLog -let logLogLevel = 0; +const logLogLevel = 0; export { AccountType, @@ -110,4 +117,4 @@ export { logLog, logLogLevel, prottle, -} +}; diff --git a/src/keyExchange.spec.ts b/src/keyExchange.spec.ts index 0fa75cc4..94635246 100644 --- a/src/keyExchange.spec.ts +++ b/src/keyExchange.spec.ts @@ -19,59 +19,30 @@ import 'mocha'; import { expect, use } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); - -use(chaiAsPromised); - -import BigNumber = require('bignumber.js'); - -import { - NameResolver, -} from '@evan.network/dbcp'; +import * as chaiAsPromised from 'chai-as-promised'; import { Ipfs } from './dfs/ipfs'; import { Ipld } from './dfs/ipld'; -import { KeyExchange, KeyExchangeOptions } from './keyExchange'; -import { Mail, Mailbox, MailboxOptions } from './mailbox'; +import { KeyExchange } from './keyExchange'; +import { Mailbox } from './mailbox'; import { Profile } from './profile/profile'; -import { RightsAndRoles } from './contracts/rights-and-roles'; import { TestUtils } from './test/test-utils'; import { accounts } from './test/accounts'; import { Onboarding } from './onboarding'; -describe('KeyExchange class', function() { +use(chaiAsPromised); + +describe('KeyExchange class', function test() { this.timeout(600000); let ipfs: Ipfs; let mailbox: Mailbox; let mailbox2: Mailbox; let keyExchange1: KeyExchange; let keyExchange2: KeyExchange; - let keyExchangeKeys: any; let web3; let ipld: Ipld; let profile: Profile; let profile2: Profile; - const random = Math.random(); - const getTestMail = (): Mail => ({ - content: { - from: accounts[0], - title: 'talking to myself', - body: `hi, me. I like random numbers, for example ${random}`, - attachments: [ - { - type: 'sharedExchangeKey', - key: '', - } - ], - }, - }); - const getTestAnswer = (parentId): Mail => ({ - parentId, - content: { - body: `but my favorite random number is ${random}`, - title: 'I like random numbers as well', - }, - }); before(async () => { web3 = TestUtils.getWeb3(); @@ -85,8 +56,8 @@ describe('KeyExchange class', function() { await Onboarding.createProfile(profile1Runtime, { accountDetails: { profileType: 'company', - accountName: 'test account' - } + accountName: 'test account', + }, }); // create profile 2 const profile2Runtime = await TestUtils.getRuntime(accounts[1]); @@ -95,7 +66,7 @@ describe('KeyExchange class', function() { accountDetails: { profileType: 'company', accountName: 'test account', - } + }, }); profile = profile1Runtime.profile; @@ -111,7 +82,7 @@ describe('KeyExchange class', function() { it('should be able to send an invitation mail and store new commKey', async () => { const foreignPubkey = await profile2.getPublicKey(); const commKey = await keyExchange1.generateCommKey(); - await keyExchange1.sendInvite(accounts[1], foreignPubkey, commKey, { fromAlias: 'Bob', }); + await keyExchange1.sendInvite(accounts[1], foreignPubkey, commKey, { fromAlias: 'Bob' }); await profile.addContactKey(accounts[1], 'commKey', commKey); await profile.storeForAccount(profile.treeLabels.addressBook); }); @@ -128,45 +99,54 @@ describe('KeyExchange class', function() { }); it('should be able retrieve the encrypted communication key with the public key of account 2', - async () => { - const result = await mailbox2.getMails(1, 0); - const keys = Object.keys(result.mails); - expect(result.mails[keys[0]].content.attachments[0].type).to.equal('commKey'); - let profileFromMail = await TestUtils.getProfile(web3, null, ipld, result.mails[keys[0]].content.from); - - const publicKeyProfile = await profileFromMail.getPublicKey(); - const commSecret = keyExchange2.computeSecretKey(publicKeyProfile); - const commKey = await keyExchange2.decryptCommKey( - result.mails[keys[0]].content.attachments[0].key, commSecret.toString('hex')); - }); + async () => { + const result = await mailbox2.getMails(1, 0); + const keys = Object.keys(result.mails); + expect(result.mails[keys[0]].content.attachments[0].type).to.equal('commKey'); + const profileFromMail = await TestUtils.getProfile( + web3, null, ipld, result.mails[keys[0]].content.from, + ); + + const publicKeyProfile = await profileFromMail.getPublicKey(); + const commSecret = keyExchange2.computeSecretKey(publicKeyProfile); + await expect( + keyExchange2.decryptCommKey( + result.mails[keys[0]].content.attachments[0].key, commSecret.toString('hex'), + ), + ) + .not.to.be.rejected; + }); it('should not be able to decrypt the communication key when a third person gets the message', - async () => { - const result = await mailbox2.getMails(1, 0); - const keys = Object.keys(result.mails); - expect(result.mails[keys[0]].content.attachments[0].type).to.equal('commKey'); - - let profileFromMail = await TestUtils.getProfile(web3, null, ipld, result.mails[keys[0]].content.from); - const keyExchangeOptions = { - mailbox, - cryptoProvider: TestUtils.getCryptoProvider(), - defaultCryptoAlgo: 'aes', - account: accounts[2], - keyProvider: TestUtils.getKeyProvider(), - }; - - const blackHat = new KeyExchange(keyExchangeOptions); - const publicKeyProfile = await profileFromMail.getPublicKey(); - const commSecret = blackHat.computeSecretKey(publicKeyProfile); - await expect( - blackHat.decryptCommKey( - result.mails[keys[0]].content.attachments[0].key, commSecret.toString('hex'))) - .to.be.rejected; - }); + async () => { + const result = await mailbox2.getMails(1, 0); + const keys = Object.keys(result.mails); + expect(result.mails[keys[0]].content.attachments[0].type).to.equal('commKey'); + + const profileFromMail = await TestUtils.getProfile( + web3, null, ipld, result.mails[keys[0]].content.from, + ); + const keyExchangeOptions = { + mailbox, + cryptoProvider: TestUtils.getCryptoProvider(), + defaultCryptoAlgo: 'aes', + account: accounts[2], + keyProvider: TestUtils.getKeyProvider(), + }; + + const blackHat = new KeyExchange(keyExchangeOptions); + const publicKeyProfile = await profileFromMail.getPublicKey(); + const commSecret = blackHat.computeSecretKey(publicKeyProfile); + await expect( + blackHat.decryptCommKey( + result.mails[keys[0]].content.attachments[0].key, commSecret.toString('hex'), + ), + ) + .to.be.rejected; + }); it('should be able to send an invitation to a remote account', async () => { - const remoteAddress = ''; - let profileLocal = await TestUtils.getProfile(web3, null, ipld, accounts[1]); + const profileLocal = await TestUtils.getProfile(web3, null, ipld, accounts[1]); const foreignPubkey = await profileLocal.getPublicKey(); const commKey = await keyExchange1.generateCommKey(); await keyExchange1.sendInvite(accounts[1], foreignPubkey, commKey, 'hi'); diff --git a/src/keyExchange.ts b/src/keyExchange.ts index 95e145b0..291abbad 100644 --- a/src/keyExchange.ts +++ b/src/keyExchange.ts @@ -17,22 +17,18 @@ the following URL: https://evan.network/license/ */ -import crypto = require('crypto-browserify'); - import { - ContractLoader, KeyProvider, Logger, LoggerOptions, - NameResolver, } from '@evan.network/dbcp'; import { Aes } from './encryption/aes'; import { CryptoProvider } from './encryption/crypto-provider'; -import { Ipld } from './dfs/ipld'; -import { Ipfs } from './dfs/ipfs'; import { Mail, Mailbox } from './mailbox'; +import crypto = require('crypto-browserify'); + /** * parameters for KeyExchange constructor @@ -55,15 +51,22 @@ export interface KeyExchangeOptions extends LoggerOptions { * @class KeyExchange (name) */ export class KeyExchange extends Logger { - private SHARED_SECRET = Buffer.from('a832d7a4c60473d4fcddabf5c31f5b64dcb2382bbebbeb7c49b6cfc2f08fe9c3', 'hex'); + private diffieHellman: any; + private COMM_KEY_CONTEXT = 'mailboxKeyExchange'; + private mailbox: Mailbox; + private cryptoProvider: CryptoProvider; + private defaultCryptoAlgo: string; + private account: string; + private keyProvider: KeyProvider; + private aes: Aes; public publicKey: string; @@ -73,7 +76,7 @@ export class KeyExchange extends Logger { * @param {KeyExchangeOptions} options * @memberof KeyExchange */ - constructor(options: KeyExchangeOptions) { + public constructor(options: KeyExchangeOptions) { super(options); this.aes = new Aes(); this.mailbox = options.mailbox; @@ -105,13 +108,15 @@ export class KeyExchange extends Logger { /** * decrypts a given communication key with an exchange key * - * @param {string} encryptedCommKey encrypted communications key received from another account - * @param {string} exchangeKey Diffie Hellman exchange key from computeSecretKey + * @param {string} encryptedCommKey encrypted communications key received from + * another account + * @param {string} exchangeKey Diffie Hellman exchange key from + * computeSecretKey * @return {Promise} commKey as a buffer */ public async decryptCommKey(encryptedCommKey: string, exchangeKey: string): Promise { const cryptor = this.cryptoProvider.getCryptorByCryptoAlgo(this.defaultCryptoAlgo); - return await cryptor.decrypt(Buffer.from(encryptedCommKey, 'hex'), { key: exchangeKey, }); + return cryptor.decrypt(Buffer.from(encryptedCommKey, 'hex'), { key: exchangeKey }); } /** @@ -123,7 +128,7 @@ export class KeyExchange extends Logger { return { publicKey: this.diffieHellman.getPublicKey(), privateKey: this.diffieHellman.getPrivateKey(), - } + }; } /** @@ -147,12 +152,12 @@ export class KeyExchange extends Logger { const ret: Mail = { content: { from, - fromAlias : mailContent.fromAlias, - fromMail : mailContent.fromMail, + fromAlias: mailContent.fromAlias, + fromMail: mailContent.fromMail, title: mailContent.title, body: mailContent.body, - attachments: mailContent.attachments || [] - } + attachments: mailContent.attachments || [], + }, }; ret.content.title = ret.content.title || 'Contact request'; @@ -162,10 +167,10 @@ I'd like to add you as a contact. Do you accept my invitation? With kind regards, -${mailContent && mailContent.fromAlias || from}`; +${(mailContent && mailContent.fromAlias) || from}`; ret.content.attachments.push({ type: 'commKey', - key: encryptedCommKey + key: encryptedCommKey, }); return ret; }; @@ -181,16 +186,21 @@ ${mailContent && mailContent.fromAlias || from}`; * @param {any} mailContent mail to send * @return {Promise} resolved when done */ - public async sendInvite(targetAccount: string, targetPublicKey: string, commKey: string, mailContent: any): Promise { + public async sendInvite( + targetAccount: string, + targetPublicKey: string, + commKey: string, + mailContent: any, + ): Promise { const secret = this.computeSecretKey(targetPublicKey).toString('hex'); const cryptor = this.cryptoProvider.getCryptorByCryptoAlgo(this.defaultCryptoAlgo); - const encryptedCommKey = await cryptor.encrypt(commKey, { key: secret, }); + const encryptedCommKey = await cryptor.encrypt(commKey, { key: secret }); await this.mailbox.sendMail( this.getExchangeMail(this.account, mailContent, encryptedCommKey.toString('hex')), this.account, targetAccount, '0', - 'mailboxKeyExchange' + 'mailboxKeyExchange', ); } diff --git a/src/mailbox.spec.ts b/src/mailbox.spec.ts index ebce8113..0a5ec9b3 100644 --- a/src/mailbox.spec.ts +++ b/src/mailbox.spec.ts @@ -19,19 +19,15 @@ import 'mocha'; import { expect } from 'chai'; -import BigNumber = require('bignumber.js'); - -import { - NameResolver, -} from '@evan.network/dbcp'; +import * as BigNumber from 'bignumber.js'; import { accounts } from './test/accounts'; import { Ipfs } from './dfs/ipfs'; import { Ipld } from './dfs/ipld'; -import { Mail, Mailbox, MailboxOptions } from './mailbox'; +import { Mail, Mailbox } from './mailbox'; import { TestUtils } from './test/test-utils'; -describe('Mailbox class', function() { +describe('Mailbox class', function test() { this.timeout(600000); let ipfs: Ipfs; let mailbox: Mailbox; @@ -46,9 +42,9 @@ describe('Mailbox class', function() { attachments: [ { type: 'sharedExchangeKey', - key: '' - } - ] + key: '', + }, + ], }, }); const getTestAnswer = (parentId): Mail => ({ @@ -68,8 +64,8 @@ describe('Mailbox class', function() { nameResolver: await TestUtils.getNameResolver(web3), ipfs, contractLoader: await TestUtils.getContractLoader(web3), - cryptoProvider: TestUtils.getCryptoProvider(), - keyProvider: TestUtils.getKeyProvider(), + cryptoProvider: TestUtils.getCryptoProvider(), + keyProvider: TestUtils.getKeyProvider(), defaultCryptoAlgo: 'aes', }); }); @@ -97,10 +93,10 @@ describe('Mailbox class', function() { it('should be able to get a set amount of mails', async () => { await mailbox.sendMail(getTestMail(accounts[0]), accounts[0], accounts[0]); let mails; - mails = await mailbox.getMails(1) + mails = await mailbox.getMails(1); expect(mails).not.to.be.undefined; expect(Object.keys(mails.mails).length).to.eq(1); - mails = await mailbox.getMails(2) + mails = await mailbox.getMails(2); expect(mails).not.to.be.undefined; expect(Object.keys(mails.mails).length).to.eq(2); }); @@ -123,8 +119,8 @@ describe('Mailbox class', function() { expect(keys.length).to.eq(1); const initialMailId = keys[0]; const answer = getTestAnswer(initialMailId); - answer.content.from = accounts[0]; - await mailbox.sendAnswer(Object.assign({}, answer), accounts[0], accounts[0]); + [answer.content.from] = accounts; + await mailbox.sendAnswer({ ...answer }, accounts[0], accounts[0]); result = await mailbox.getAnswersForMail(initialMailId); expect(result).not.to.be.undefined; @@ -139,13 +135,13 @@ describe('Mailbox class', function() { it('should be able to read mails sent from another user', async () => { const startTime = Date.now(); // mailbox user 2 - const mailbox2 = new Mailbox({ + const mailbox2 = new Mailbox({ mailboxOwner: accounts[1], nameResolver: await TestUtils.getNameResolver(web3), ipfs, contractLoader: await TestUtils.getContractLoader(web3), - cryptoProvider: TestUtils.getCryptoProvider(), - keyProvider: TestUtils.getKeyProvider(), + cryptoProvider: TestUtils.getCryptoProvider(), + keyProvider: TestUtils.getKeyProvider(), defaultCryptoAlgo: 'aes', }); @@ -162,27 +158,31 @@ describe('Mailbox class', function() { }); it('should be able to send UTC tokens with a mail', async () => { - const startTime = Date.now(); await mailbox.init(); const balanceToSend = new BigNumber(web3.utils.toWei('1', 'kWei')); const balanceBefore = new BigNumber(await web3.eth.getBalance(accounts[0])); - const mailboxBalanceBefore = new BigNumber(await web3.eth.getBalance(mailbox.mailboxContract.options.address)); + const mailboxBalanceBefore = new BigNumber( + await web3.eth.getBalance(mailbox.mailboxContract.options.address), + ); await mailbox.sendMail(getTestMail(accounts[1]), accounts[0], accounts[1], `0x${balanceToSend.toString(16)}`); - const mailboxBalanceAfter = new BigNumber(await web3.eth.getBalance(mailbox.mailboxContract.options.address)); + const mailboxBalanceAfter = new BigNumber( + await web3.eth.getBalance(mailbox.mailboxContract.options.address), + ); const balanceAfter = new BigNumber(await web3.eth.getBalance(accounts[0])); - expect(balanceAfter.plus(balanceToSend).lte(balanceBefore)).to.be.true; // before - cost = after + value // (sender pays cost) - expect(mailboxBalanceBefore.plus(balanceToSend).eq(mailboxBalanceAfter)).to.be.true; // before + value = after + // before - cost = after + value // (sender pays cost) + expect(balanceAfter.plus(balanceToSend).lte(balanceBefore)).to.be.true; + // before + value = after + expect(mailboxBalanceBefore.plus(balanceToSend).eq(mailboxBalanceAfter)).to.be.true; }); it('should allow checking balance for a mail', async () => { - const startTime = Date.now(); - const mailbox2 = new Mailbox({ + const mailbox2 = new Mailbox({ mailboxOwner: accounts[1], nameResolver: await TestUtils.getNameResolver(web3), ipfs, contractLoader: await TestUtils.getContractLoader(web3), - cryptoProvider: TestUtils.getCryptoProvider(), - keyProvider: TestUtils.getKeyProvider(), + cryptoProvider: TestUtils.getCryptoProvider(), + keyProvider: TestUtils.getKeyProvider(), defaultCryptoAlgo: 'aes', }); const balanceToSend = new BigNumber(web3.utils.toWei('0.1', 'Ether')); @@ -194,14 +194,13 @@ describe('Mailbox class', function() { }); it('should allow withdrawing UTC tokens for a mail', async () => { - const startTime = Date.now(); - const mailbox2 = new Mailbox({ + const mailbox2 = new Mailbox({ mailboxOwner: accounts[1], nameResolver: await TestUtils.getNameResolver(web3), ipfs, contractLoader: await TestUtils.getContractLoader(web3), - cryptoProvider: TestUtils.getCryptoProvider(), - keyProvider: TestUtils.getKeyProvider(), + cryptoProvider: TestUtils.getCryptoProvider(), + keyProvider: TestUtils.getKeyProvider(), defaultCryptoAlgo: 'aes', }); const balanceToSend = new BigNumber(web3.utils.toWei('0.1', 'Ether')); @@ -209,13 +208,17 @@ describe('Mailbox class', function() { const result = await mailbox2.getMails(1, 0); const keys = Object.keys(result.mails); const balanceBefore = new BigNumber(await web3.eth.getBalance(accounts[1])); - const mailboxBalanceBefore = new BigNumber(await web3.eth.getBalance(mailbox.mailboxContract.options.address)); + const mailboxBalanceBefore = new BigNumber( + await web3.eth.getBalance(mailbox.mailboxContract.options.address), + ); await mailbox2.withdrawFromMail(keys[0], accounts[1]); - const mailboxBalanceAfter = new BigNumber(await web3.eth.getBalance(mailbox.mailboxContract.options.address)); + const mailboxBalanceAfter = new BigNumber( + await web3.eth.getBalance(mailbox.mailboxContract.options.address), + ); const balanceAfter = new BigNumber(await web3.eth.getBalance(accounts[1])); - expect(balanceBefore.plus(balanceToSend).gte(balanceAfter)).to.be.true; // before + value - cost = after // (withdrawer pays cost) - expect(mailboxBalanceAfter.plus(balanceToSend).eq(mailboxBalanceBefore)).to.be.true; // before - value = after + // before + value - cost = after // (withdrawer pays cost) + expect(balanceBefore.plus(balanceToSend).gte(balanceAfter)).to.be.true; + // before - value = after + expect(mailboxBalanceAfter.plus(balanceToSend).eq(mailboxBalanceBefore)).to.be.true; }); - - it('should now allow withdrawing UTC tokens for a mail that has no tokens', async () => {}); }); diff --git a/src/mailbox.ts b/src/mailbox.ts index 3f215d77..7dfae40a 100644 --- a/src/mailbox.ts +++ b/src/mailbox.ts @@ -19,39 +19,42 @@ import { ContractLoader, + Executor, Logger, LoggerOptions, NameResolver, KeyProvider, } from '@evan.network/dbcp'; -import { CryptoProvider } from './encryption/crypto-provider'; -import { Ipld } from './dfs/ipld'; -import { Ipfs } from './dfs/ipfs'; +import { + CryptoProvider, + Ipld, + Ipfs, +} from './index'; /** * mail object */ export interface Mail { content: { - attachments?: any[], - body?: string, - from?: string, - fromAlias?: string, - fromMail?: string, - sent?: number, - title?: string, - to?: string, - }, - answers?: MailboxResult, - parentId?: string, + attachments?: any[]; + body?: string; + from?: string; + fromAlias?: string; + fromMail?: string; + sent?: number; + title?: string; + to?: string; + }; + answers?: MailboxResult; + parentId?: string; } /** * collection of mails */ export interface MailboxResult { - mails: { [index: string]: Mail }, + mails: { [index: string]: Mail }; totalResultCount: number; } @@ -59,13 +62,14 @@ export interface MailboxResult { * parameters for Mailbox constructor */ export interface MailboxOptions extends LoggerOptions { - mailboxOwner: string; - nameResolver: NameResolver; - ipfs: Ipfs; contractLoader: ContractLoader; cryptoProvider: CryptoProvider; - keyProvider: KeyProvider; defaultCryptoAlgo: string; + ipfs: Ipfs; + keyProvider: KeyProvider; + mailboxOwner: string; + nameResolver: NameResolver; + executor?: Executor; } /** @@ -74,87 +78,72 @@ export interface MailboxOptions extends LoggerOptions { * @class Mailbox (name) */ export class Mailbox extends Logger { - mailboxOwner: string; - nameResolver: NameResolver; - contractLoader: ContractLoader; - mailboxContract: any; - ipfs: Ipfs; - cryptoProvider: CryptoProvider; - keyProvider: KeyProvider; - defaultCryptoAlgo: string; - initialized: boolean; + public contractLoader: ContractLoader; + + public cryptoProvider: CryptoProvider; + + public defaultCryptoAlgo: string; + + public executor: Executor; + + public initialized: boolean; + + public ipfs: Ipfs; + + public keyProvider: KeyProvider; + + public mailboxContract: any; + + public mailboxOwner: string; - constructor(options: MailboxOptions) { + public nameResolver: NameResolver; + + public constructor(options: MailboxOptions) { super(options); - this.mailboxOwner = options.mailboxOwner; - this.nameResolver = options.nameResolver; this.contractLoader = options.contractLoader; - this.ipfs = options.ipfs; - this.keyProvider = options.keyProvider; this.cryptoProvider = options.cryptoProvider; this.defaultCryptoAlgo = options.defaultCryptoAlgo; + this.ipfs = options.ipfs; + this.keyProvider = options.keyProvider; + this.mailboxOwner = options.mailboxOwner; + this.nameResolver = options.nameResolver; + this.executor = options.executor || options.nameResolver.executor; } /** - * initialize mailbox module - * - * @return {Promise} resolved when done - */ - async init(): Promise { - if (this.initialized) { - return; - } else { - const domain = this.nameResolver.getDomainName(this.nameResolver.config.domains.mailbox); - const address = await this.nameResolver.getAddress(domain); - this.mailboxContract = this.contractLoader.loadContract('MailBoxInterface', address); - this.initialized = true; - } - } - - async getSentMails(count = 10, offset = 0) { - return await this.getMails(count, offset, 'Sent'); - } - - /** - * gets received from mailboxOwner - * - * @param {number} count number of mails to retrieve (default 10) - * @param {number} offset mail offset (default 0) - * @return {Promise} The received mails. - */ - async getReceivedMails(count = 10, offset = 0) { - return await this.getMails(count, offset, 'Received'); - } - - /** - * Gets the last n mails, resolved contents + * Gets answer tree for mail, traverses subanswers as well * - * @param {number} count retrieve up to this many answers (for paging) - * @param {number} offset skip this many answers (for paging) - * @param {string} type retrieve sent or received mails - * @return {Promise} resolved mails + * @param {string} mailId mail to resolve + * @param {number} count retrieve up to this many answers (for paging) + * @param {number} offset skip this many answers (for paging) + * @return {Promise} answer tree for mail */ - async getMails(count = 10, offset = 0, type = 'Received'): Promise { + public async getAnswersForMail(mailId: string, count = 5, offset = 0): Promise { await this.init(); const results: MailboxResult = { mails: {}, totalResultCount: 0, }; - const executor = this.nameResolver.executor; - const listAddressHash = await executor.executeContractCall( - this.mailboxContract, `getMy${type}Mails`, { from: this.mailboxOwner, }); + const listAddressHash = await this.executor.executeContractCall( + this.mailboxContract, 'getAnswersForMail', mailId, { from: this.mailboxOwner }, + ); if (listAddressHash !== '0x0000000000000000000000000000000000000000000000000000000000000000') { const listAddress = this.nameResolver.bytes32ToAddress(listAddressHash); const listContract = this.contractLoader.loadContract('DataStoreList', listAddress); - const listLength = await executor.executeContractCall(listContract, 'length'); - results.totalResultCount = parseInt(listLength.toString(), 10); + const listLength = await this.executor.executeContractCall(listContract, 'length'); + results.totalResultCount = parseInt(listLength, 10); if (results.totalResultCount) { - let ipld: Ipld; - const mailIds = await this.nameResolver.getArrayFromListContract(listContract, count, offset, true); - const originator = this.nameResolver.soliditySha3.apply(this.nameResolver, [ - this.nameResolver.soliditySha3(this.mailboxOwner), this.nameResolver.soliditySha3(this.mailboxOwner), ].sort()); - ipld = new Ipld({ + const mailIds = await this.nameResolver.getArrayFromListContract( + listContract, count, offset, true, + ); + const originator = this.nameResolver.soliditySha3( + ...[ + this.nameResolver.soliditySha3(this.mailboxOwner), + this.nameResolver.soliditySha3(this.mailboxOwner), + ].sort(), + ); + const ipld = new Ipld({ ipfs: this.ipfs, keyProvider: this.keyProvider, cryptoProvider: this.cryptoProvider, @@ -162,39 +151,63 @@ export class Mailbox extends Logger { originator, nameResolver: this.nameResolver, }); - for (let mailId of mailIds) { + for (const answerId of mailIds) { try { - const mailResult = await executor.executeContractCall(this.mailboxContract, 'getMail', mailId); + const mailResult = await this.executor.executeContractCall( + this.mailboxContract, 'getMail', answerId, + ); const mail = await ipld.getLinkedGraph(mailResult.data); const hashedSender = this.nameResolver.soliditySha3(mail.content.from); if (hashedSender !== mailResult.sender) { - throw new Error(`mail claims to be sent from ${hashedSender}, but was sent from ${mailResult.sender}`); + throw new Error(`mail claims to be sent from ${hashedSender}, ` + + `but was sent from ${mailResult.sender}`); } else { - results.mails[mailId] = mail; + results.mails[answerId] = mail; } } catch (ex) { - this.log(`could not unpack mail: "${mailId}"; ${ex.message || ex}`, 'warning'); - results.mails[mailId] = null; + this.log(`could not unpack answer: "${answerId}"; ${ex.message || ex}`, 'warning'); + results.mails[answerId] = null; } } } } + return results; } + /** + * returns amount of UTC deposited for a mail + * + * @param {string} mailId mail to resolve + * @return {Promise} balance of the mail in Wei can be converted with + * web3[.utils].fromWei(...) + */ + public async getBalanceFromMail(mailId: string): Promise { + await this.init(); + // mailboxOwner + return this.executor.executeContractCall( + this.mailboxContract, + 'getBalanceFromMail', + mailId, + { from: this.mailboxOwner }, + ); + } + /** * Gets one single mail directly * * @param {string} mail mail to resolve (mailId or hash) * @return {Promise} The mail. */ - async getMail(mail: string): Promise { + public async getMail(mail: string): Promise { await this.init(); - const executor = this.nameResolver.executor; - let ipld: Ipld; - const originator = this.nameResolver.soliditySha3.apply(this.nameResolver, [ - this.nameResolver.soliditySha3(this.mailboxOwner), this.nameResolver.soliditySha3(this.mailboxOwner), ].sort()); - ipld = new Ipld({ + const originator = this.nameResolver.soliditySha3( + ...[ + this.nameResolver.soliditySha3(this.mailboxOwner), + this.nameResolver.soliditySha3(this.mailboxOwner), + ].sort(), + ); + const ipld = new Ipld({ ipfs: this.ipfs, keyProvider: this.keyProvider, cryptoProvider: this.cryptoProvider, @@ -206,15 +219,14 @@ export class Mailbox extends Logger { try { if (mail.startsWith('Qm')) { return await ipld.getLinkedGraph(mail); + } + const mailResult = await this.executor.executeContractCall(this.mailboxContract, 'getMail', mail); + const mailItem = await ipld.getLinkedGraph(mailResult.data); + const hashedSender = this.nameResolver.soliditySha3(mailItem.content.from); + if (hashedSender !== mailResult.sender) { + throw new Error(`mail claims to be sent from ${hashedSender}, but was sent from ${mailResult.sender}`); } else { - const mailResult = await executor.executeContractCall(this.mailboxContract, 'getMail', mail); - const mailItem = await ipld.getLinkedGraph(mailResult.data); - const hashedSender = this.nameResolver.soliditySha3(mailItem.content.from); - if (hashedSender !== mailResult.sender) { - throw new Error(`mail claims to be sent from ${hashedSender}, but was sent from ${mailResult.sender}`); - } else { - return mailItem; - } + return mailItem; } } catch (ex) { this.log(`could not decrypt mail: "${mail}"; ${ex.message || ex}`, 'warning'); @@ -223,34 +235,39 @@ export class Mailbox extends Logger { } /** - * Gets answer tree for mail, traverses subanswers as well + * Gets the last n mails, resolved contents * - * @param {string} mailId mail to resolve - * @param {number} count retrieve up to this many answers (for paging) - * @param {number} offset skip this many answers (for paging) - * @return {Promise} answer tree for mail + * @param {number} count retrieve up to this many answers (for paging) + * @param {number} offset skip this many answers (for paging) + * @param {string} type retrieve sent or received mails + * @return {Promise} resolved mails */ - async getAnswersForMail(mailId: string, count = 5, offset = 0): Promise { + public async getMails(count = 10, offset = 0, type = 'Received'): Promise { await this.init(); const results: MailboxResult = { mails: {}, totalResultCount: 0, }; - const executor = this.nameResolver.executor; - const listAddressHash = await executor.executeContractCall( - this.mailboxContract, 'getAnswersForMail', mailId, { from: this.mailboxOwner, }); + const listAddressHash = await this.executor.executeContractCall( + this.mailboxContract, `getMy${type}Mails`, { from: this.mailboxOwner }, + ); if (listAddressHash !== '0x0000000000000000000000000000000000000000000000000000000000000000') { const listAddress = this.nameResolver.bytes32ToAddress(listAddressHash); const listContract = this.contractLoader.loadContract('DataStoreList', listAddress); - const listLength = await executor.executeContractCall(listContract, 'length'); - results.totalResultCount = parseInt(listLength, 10); + const listLength = await this.executor.executeContractCall(listContract, 'length'); + results.totalResultCount = parseInt(listLength.toString(), 10); if (results.totalResultCount) { - let ipld: Ipld; - const mailIds = await this.nameResolver.getArrayFromListContract(listContract, count, offset, true); - const originator = this.nameResolver.soliditySha3.apply(this.nameResolver, [ - this.nameResolver.soliditySha3(this.mailboxOwner), this.nameResolver.soliditySha3(this.mailboxOwner), ].sort()); - ipld = new Ipld({ + const mailIds = await this.nameResolver.getArrayFromListContract( + listContract, count, offset, true, + ); + const originator = this.nameResolver.soliditySha3( + ...[ + this.nameResolver.soliditySha3(this.mailboxOwner), + this.nameResolver.soliditySha3(this.mailboxOwner), + ].sort(), + ); + const ipld = new Ipld({ ipfs: this.ipfs, keyProvider: this.keyProvider, cryptoProvider: this.cryptoProvider, @@ -258,61 +275,53 @@ export class Mailbox extends Logger { originator, nameResolver: this.nameResolver, }); - for (let answerId of mailIds) { + for (const mailId of mailIds) { try { - const mailResult = await executor.executeContractCall(this.mailboxContract, 'getMail', answerId); + const mailResult = await this.executor.executeContractCall(this.mailboxContract, 'getMail', mailId); const mail = await ipld.getLinkedGraph(mailResult.data); const hashedSender = this.nameResolver.soliditySha3(mail.content.from); if (hashedSender !== mailResult.sender) { throw new Error(`mail claims to be sent from ${hashedSender}, but was sent from ${mailResult.sender}`); } else { - results.mails[answerId] = mail; + results.mails[mailId] = mail; } } catch (ex) { - this.log(`could not unpack answer: "${answerId}"; ${ex.message || ex}`, 'warning'); - results.mails[answerId] = null; + this.log(`could not unpack mail: "${mailId}"; ${ex.message || ex}`, 'warning'); + results.mails[mailId] = null; } } } } - return results; } /** - * sends a mail to given target + * gets received from mailboxOwner + * + * @param {number} count number of mails to retrieve (default 10) + * @param {number} offset mail offset (default 0) + * @return {Promise} The received mails. + */ + public async getReceivedMails(count = 10, offset = 0) { + return this.getMails(count, offset, 'Received'); + } + + public async getSentMails(count = 10, offset = 0) { + return this.getMails(count, offset, 'Sent'); + } + + /** + * initialize mailbox module * - * @param {Mail} mail a mail to send - * @param {string} from account id to send mail from - * @param {string} to account id to send mail to - * @param {string} value (optional) UTC amount to send with mail in Wei can be - * created with web3[.utils].toWei(...) - * @param {string} context encrypt mail with different context * @return {Promise} resolved when done */ - async sendMail(mail: Mail, from: string, to: string, value = '0', context?: string): Promise { - await this.init(); - mail.content.from = from; - mail.content.sent = new Date().getTime(); - mail.content.to = to; - const combinedHash = this.nameResolver.soliditySha3.apply(this.nameResolver, - [this.nameResolver.soliditySha3(to), this.nameResolver.soliditySha3(this.mailboxOwner)].sort()); - const ipld: Ipld = new Ipld({ - ipfs: this.ipfs, - keyProvider: this.keyProvider, - cryptoProvider: this.cryptoProvider, - originator: context ? this.nameResolver.soliditySha3(context) : combinedHash, - defaultCryptoAlgo: this.defaultCryptoAlgo, - nameResolver: this.nameResolver, - }); - const hash = await ipld.store(mail); - await this.nameResolver.executor.executeContractTransaction( - this.mailboxContract, - 'sendMail', - { from, autoGas: 1.1, value, }, - [ to ], - hash, - ); + public async init(): Promise { + if (!this.initialized) { + const domain = this.nameResolver.getDomainName(this.nameResolver.config.domains.mailbox); + const address = await this.nameResolver.getAddress(domain); + this.mailboxContract = this.contractLoader.loadContract('MailBoxInterface', address); + this.initialized = true; + } } /** @@ -325,11 +334,16 @@ export class Mailbox extends Logger { * created with web3[.utils].toWei(...) * @return {Promise} resolved when done */ - async sendAnswer(mail: Mail, from: string, to: string, value = '0'): Promise { + public async sendAnswer(mail: Mail, from: string, to: string, value = '0'): Promise { await this.init(); + // eslint-disable-next-line no-param-reassign mail.content.sent = new Date().getTime(); - const combinedHash = this.nameResolver.soliditySha3.apply(this.nameResolver, - [this.nameResolver.soliditySha3(to), this.nameResolver.soliditySha3(this.mailboxOwner)].sort()); + const combinedHash = this.nameResolver.soliditySha3( + ...[ + this.nameResolver.soliditySha3(to), + this.nameResolver.soliditySha3(this.mailboxOwner), + ].sort(), + ); const ipld: Ipld = new Ipld({ ipfs: this.ipfs, keyProvider: this.keyProvider, @@ -338,33 +352,58 @@ export class Mailbox extends Logger { defaultCryptoAlgo: this.defaultCryptoAlgo, nameResolver: this.nameResolver, }); - const parentId = mail.parentId; + const { parentId } = mail; const hash = await ipld.store(mail); - await this.nameResolver.executor.executeContractTransaction( + await this.executor.executeContractTransaction( this.mailboxContract, 'sendAnswer', - { from, autoGas: 1.1, value, }, - [ to ], + { from, autoGas: 1.1, value }, + [to], hash, parentId, ); } /** - * returns amount of UTC deposited for a mail + * sends a mail to given target * - * @param {string} mailId mail to resolve - * @return {Promise} balance of the mail in Wei can be converted with - * web3[.utils].fromWei(...) + * @param {Mail} mail a mail to send + * @param {string} from account id to send mail from + * @param {string} to account id to send mail to + * @param {string} value (optional) UTC amount to send with mail in Wei can be + * created with web3[.utils].toWei(...) + * @param {string} context encrypt mail with different context + * @return {Promise} resolved when done */ - async getBalanceFromMail(mailId: string): Promise { + public async sendMail(mail: Mail, from: string, to: string, value = '0', context?: string): Promise { await this.init(); - // mailboxOwner - return this.nameResolver.executor.executeContractCall( + // eslint-disable-next-line no-param-reassign + mail.content.from = from; + // eslint-disable-next-line no-param-reassign + mail.content.sent = new Date().getTime(); + // eslint-disable-next-line no-param-reassign + mail.content.to = to; + const combinedHash = this.nameResolver.soliditySha3( + ...[ + this.nameResolver.soliditySha3(to), + this.nameResolver.soliditySha3(this.mailboxOwner), + ].sort(), + ); + const ipld: Ipld = new Ipld({ + ipfs: this.ipfs, + keyProvider: this.keyProvider, + cryptoProvider: this.cryptoProvider, + originator: context ? this.nameResolver.soliditySha3(context) : combinedHash, + defaultCryptoAlgo: this.defaultCryptoAlgo, + nameResolver: this.nameResolver, + }); + const hash = await ipld.store(mail); + await this.executor.executeContractTransaction( this.mailboxContract, - 'getBalanceFromMail', - mailId, - { from: this.mailboxOwner, }, + 'sendMail', + { from, autoGas: 1.1, value }, + [to], + hash, ); } @@ -375,13 +414,13 @@ export class Mailbox extends Logger { * @param {string} recipient account, that receives the EVEs * @return {Promise} resolved when done */ - async withdrawFromMail(mailId: string, recipient: string): Promise { + public async withdrawFromMail(mailId: string, recipient: string): Promise { await this.init(); // mailboxOwner - await this.nameResolver.executor.executeContractTransaction( + await this.executor.executeContractTransaction( this.mailboxContract, 'withdrawFromMail', - { from: this.mailboxOwner, autoGas: 1.1, }, + { from: this.mailboxOwner, autoGas: 1.1 }, mailId, recipient, ); diff --git a/src/name-resolver.spec.ts b/src/name-resolver.spec.ts index 4cab692a..267e185e 100644 --- a/src/name-resolver.spec.ts +++ b/src/name-resolver.spec.ts @@ -19,14 +19,14 @@ import 'mocha'; import { ContractLoader, Executor } from '@evan.network/dbcp'; import { expect, use } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); +import * as chaiAsPromised from 'chai-as-promised'; import { accounts } from './test/accounts'; import { configTestcore as config } from './config-testcore'; import { NameResolver } from './name-resolver'; import { TestUtils } from './test/test-utils'; -const [ domainOwner, domainNonOwner, ensOwner, registrarOwner ] = accounts; +const [domainOwner, domainNonOwner, ensOwner] = accounts; use(chaiAsPromised); @@ -34,11 +34,10 @@ use(chaiAsPromised); const fifsRegistrarDomain = 'fifs.registrar.test.evan'; const payableRegistrarDomain = 'payable'; -describe('NameResolver class', function() { +describe('NameResolver class', function test() { this.timeout(600000); let contractLoader: ContractLoader; let executor: Executor; - let fifsRegistrar: any; let nameResolver: NameResolver; let web3; @@ -46,7 +45,8 @@ describe('NameResolver class', function() { const getWrongPrice = () => web3.utils.toWei('4', 'wei'); function prepareData(registrarDomain) { const randomAccount = web3.utils.toChecksumAddress( - `0x${[...Array(40)].map(() => Math.floor(Math.random() * 16).toString(16)).join('')}`); + `0x${[...Array(40)].map(() => Math.floor(Math.random() * 16).toString(16)).join('')}`, + ); const randomNode = Math.random().toString(32).substr(2); const domain = `${randomNode}.${registrarDomain}`; const domainHash = nameResolver.namehash(domain); @@ -65,19 +65,6 @@ describe('NameResolver class', function() { contractLoader = await TestUtils.getContractLoader(web3); executor = await TestUtils.getExecutor(web3); nameResolver = await TestUtils.getNameResolver(web3); - // create fifs registrar for testing and give it ownership over 'registrar.test.evan' - const ens = contractLoader.loadContract('AbstractENS', config.nameResolver.ensAddress); - // fifsRegistrar = await executor.createContract( - // 'FIFSRegistrar', - // [config.nameResolver.ensAddress, nameResolver.namehash('fifs.registrar.test.evan')], - // { from: registrarOwner, gas: 1000000, }, - // ); - // await nameResolver.setAddress( - // fifsRegistrarDomain, - // fifsRegistrar.options.address, - // registrarOwner, - // fifsRegistrar.options.address, - // ); }); describe('when working with the fifs registrar', () => { @@ -90,7 +77,8 @@ describe('NameResolver class', function() { await nameResolver.claimAddress(data.domain, domainOwner, data.randomAccount); // check again expect(await executor.executeContractCall( - data.ens, 'owner', data.domainHash)).to.eq(data.randomAccount); + data.ens, 'owner', data.domainHash, + )).to.eq(data.randomAccount); }); it('should not allow to take a fifs claimed domain from another account', async () => { @@ -102,30 +90,31 @@ describe('NameResolver class', function() { await nameResolver.claimAddress(data.domain, domainNonOwner, data.randomAccount); // check again expect(await executor.executeContractCall( - data.ens, 'owner', data.domainHash)).to.eq(data.randomAccount); + data.ens, 'owner', data.domainHash, + )).to.eq(data.randomAccount); // try to claim address, that is already onwed by data.randomAccount const addressTakeover = nameResolver.claimAddress(data.domain, domainNonOwner); await expect(addressTakeover).to.be.rejected; }); }); - describe('when buying domains', async() => { + describe('when buying domains', async () => { let payableRegistrar: any; let nameResolverTimed: NameResolver; let timedEns: any; // use modified validity durations - let timeValidMs = 45000; - let timeValidPreExpireWindowMs = -22500; - let timeValidPostExpireWindowMs = 22500; + const timeValidMs = 45000; + const timeValidPreExpireWindowMs = -22500; + const timeValidPostExpireWindowMs = 22500; const createStructure = async () => { // create new ens and register test tld timedEns = await executor.createContract('TimedENS', [], { from: ensOwner, gas: 1000000 }); - const [ resolver ] = await Promise.all([ + const [resolver] = await Promise.all([ // create new resolver, tied to custom ens executor.createContract( 'PublicResolver', - [ timedEns.options.address ], + [timedEns.options.address], { from: ensOwner, gas: 2000000 }, ), // set duration, that an owner still has excluse claim permission after validity expired @@ -164,7 +153,7 @@ describe('NameResolver class', function() { nameResolverTimed.namehash('payable'), getPrice(), ], - { from: ensOwner, gas: 1000000, }, + { from: ensOwner, gas: 1000000 }, ); // set duration and pre expiration window await Promise.all([ @@ -197,60 +186,73 @@ describe('NameResolver class', function() { }); describe('when working with the payable registrar', () => { - it('should be able to claim a new domain from a payable registrar', async() => { + it('should be able to claim a new domain from a payable registrar', async () => { const data = prepareData(payableRegistrarDomain); // check owner at ens const oldOwner = await executor.executeContractCall(nameResolverTimed.ensContract, 'owner', data.domainHash); expect(oldOwner).not.to.eq(data.randomAccount); // claim domain - await nameResolverTimed.claimAddress(data.domain, domainOwner, data.randomAccount, getPrice()); + await nameResolverTimed.claimAddress( + data.domain, domainOwner, data.randomAccount, getPrice(), + ); // check again expect(await executor.executeContractCall( - nameResolverTimed.ensContract, 'owner', data.domainHash)).to.eq(data.randomAccount); + nameResolverTimed.ensContract, 'owner', data.domainHash, + )).to.eq(data.randomAccount); }); - it('should not allow to take a payable claimed domain when giving wrong funds', async() => { + it('should not allow to take a payable claimed domain when giving wrong funds', async () => { const data = prepareData(payableRegistrarDomain); // check owner at ens const oldOwner = await executor.executeContractCall(nameResolverTimed.ensContract, 'owner', data.domainHash); expect(oldOwner).not.to.eq(data.randomAccount); // try to claim domain await expect( - nameResolverTimed.claimAddress(data.domain, domainOwner, data.randomAccount, getWrongPrice()), + nameResolverTimed.claimAddress( + data.domain, domainOwner, data.randomAccount, getWrongPrice(), + ), ).to.be.rejected; }); - it('should not allow to take a payable claimed domain from another account', async() => { + it('should not allow to take a payable claimed domain from another account', async () => { const data = prepareData(payableRegistrarDomain); // check owner at ens const oldOwner = await executor.executeContractCall(nameResolverTimed.ensContract, 'owner', data.domainHash); expect(oldOwner).not.to.eq(data.randomAccount); // claim domain - await nameResolverTimed.claimAddress(data.domain, domainNonOwner, data.randomAccount, getPrice()); + await nameResolverTimed.claimAddress( + data.domain, domainNonOwner, data.randomAccount, getPrice(), + ); // check again expect(await executor.executeContractCall( - nameResolverTimed.ensContract, 'owner', data.domainHash)).to.eq(data.randomAccount); + nameResolverTimed.ensContract, 'owner', data.domainHash, + )).to.eq(data.randomAccount); // try to claim address, that is already onwed by data.randomAccount - const addressTakeover = nameResolverTimed.claimAddress(data.domain, domainNonOwner, domainNonOwner, getPrice()); + const addressTakeover = nameResolverTimed.claimAddress( + data.domain, domainNonOwner, domainNonOwner, getPrice(), + ); await expect(addressTakeover).to.be.rejected; }); describe('when using ens owner account', () => { - it('should allow to take a payable claimed domain from another account as registrar owner', async() => { + it('should allow to take a payable claimed domain from another account as registrar owner', async () => { const data = prepareData(payableRegistrarDomain); // check owner at ens const oldOwner = await executor.executeContractCall(nameResolverTimed.ensContract, 'owner', data.domainHash); expect(oldOwner).not.to.eq(data.randomAccount); // claim domain - await nameResolverTimed.claimAddress(data.domain, domainNonOwner, data.randomAccount, getPrice()); + await nameResolverTimed.claimAddress( + data.domain, domainNonOwner, data.randomAccount, getPrice(), + ); // check again expect(await executor.executeContractCall( - nameResolverTimed.ensContract, 'owner', data.domainHash)).to.eq(data.randomAccount); + nameResolverTimed.ensContract, 'owner', data.domainHash, + )).to.eq(data.randomAccount); // try to claim address, that is already onwed by data.randomAccount await nameResolverTimed.claimAddress(data.domain, ensOwner, ensOwner, getPrice()); }); - it('should allow to change the price', async() => { + it('should allow to change the price', async () => { const data = prepareData(payableRegistrarDomain); expect(await nameResolverTimed.getPrice(data.domain)).to.eq(getPrice()); const newPrice = Math.floor(Math.random() * 10000).toString(); @@ -260,32 +262,36 @@ describe('NameResolver class', function() { await nameResolverTimed.setPrice(payableRegistrarDomain, ensOwner, getPrice()); }); - it('should allow to claim existing funds from registrar contract', async() => { + it('should allow to claim existing funds from registrar contract', async () => { const data = prepareData(payableRegistrarDomain); // claim domain to ensure funds on contract - await nameResolverTimed.claimAddress(data.domain, domainOwner, data.randomAccount, getPrice()); + await nameResolverTimed.claimAddress( + data.domain, domainOwner, data.randomAccount, getPrice(), + ); const registrarAddress = await executor.executeContractCall( - nameResolverTimed.ensContract, 'owner', nameResolverTimed.namehash(payableRegistrarDomain)); + nameResolverTimed.ensContract, 'owner', nameResolverTimed.namehash(payableRegistrarDomain), + ); expect(await web3.eth.getBalance(registrarAddress)).not.to.eq('0'); - const registrar = contractLoader.loadContract('PayableRegistrar', registrarAddress); await nameResolverTimed.claimFunds(payableRegistrarDomain, ensOwner); expect(await web3.eth.getBalance(registrarAddress)).to.eq('0'); }); - it('should not allow to claim existing funds from non registrar owner contract', async() => { + it('should not allow to claim existing funds from non registrar owner contract', async () => { const data = prepareData(payableRegistrarDomain); // claim domain to ensure funds on contract - await nameResolverTimed.claimAddress(data.domain, domainOwner, data.randomAccount, getPrice()); + await nameResolverTimed.claimAddress( + data.domain, domainOwner, data.randomAccount, getPrice(), + ); const registrarAddress = await executor.executeContractCall( - nameResolverTimed.ensContract, 'owner', nameResolverTimed.namehash(payableRegistrarDomain)); + nameResolverTimed.ensContract, 'owner', nameResolverTimed.namehash(payableRegistrarDomain), + ); expect(await web3.eth.getBalance(registrarAddress)).not.to.eq('0'); - const registrar = contractLoader.loadContract('PayableRegistrar', registrarAddress); await expect(nameResolverTimed.claimFunds(domainOwner, ensOwner)).to.be.rejected; }); }); }); - describe('when using an ENS with a time limited validity for nodes', async() => { + describe('when using an ENS with a time limited validity for nodes', async () => { it('allows setting and getting addresses', async () => { const domain = `sample_${Math.random().toString(36).substr(2)}.ownedbyensowner`; const randomAddress = TestUtils.getRandomAddress(); @@ -302,7 +308,9 @@ describe('NameResolver class', function() { expect(await nameResolverTimed.getAddress(domain)).to.eq(randomAddress); // set valid time - const setValidUntilP = nameResolverTimed.setValidUntil(domain, ensOwner, Date.now() + timeValidMs); + const setValidUntilP = nameResolverTimed.setValidUntil( + domain, ensOwner, Date.now() + timeValidMs, + ); await expect(setValidUntilP).not.to.be.rejected; }); @@ -315,7 +323,9 @@ describe('NameResolver class', function() { expect(await nameResolverTimed.getAddress(domain)).to.eq(randomAddress); // set valid time - const setValidUntilP = nameResolverTimed.setValidUntil(domain, domainNonOwner, Date.now() + timeValidMs); + const setValidUntilP = nameResolverTimed.setValidUntil( + domain, domainNonOwner, Date.now() + timeValidMs, + ); await expect(setValidUntilP).to.be.rejected; }); @@ -342,8 +352,8 @@ describe('NameResolver class', function() { expect(await nameResolverTimed.getAddress(domain)).to.eq(null); }); - it('does not re-enable subdomain resolval, ' + - 'when refreshing domains between domain to resolve and an expired upper domain', async () => { + it('does not re-enable subdomain resolval, ' + + 'when refreshing domains between domain to resolve and an expired upper domain', async () => { const domain = `sample_${Math.random().toString(36).substr(2)}.ownedbyensowner`; const subdomain = `sub.${domain}`; @@ -397,12 +407,12 @@ describe('NameResolver class', function() { await nameResolverTimed.claimAddress(domain, domainOwner, randomAccount, getPrice()); // check again expect(await executor.executeContractCall( - timedEns, 'owner', domainHash)).to.eq(randomAccount); + timedEns, 'owner', domainHash, + )).to.eq(randomAccount); }); it('has expiring durations as set up', async () => { const domain = `sample_${Math.random().toString(36).substr(2)}.payable`; - const domainHash = nameResolverTimed.namehash(domain); const randomAddress = TestUtils.getRandomAddress(); // claim domain @@ -424,7 +434,6 @@ describe('NameResolver class', function() { it('cannot extend valid duration via registrar before expiration (and before extension timeframe)', async () => { const domain = `sample_${Math.random().toString(36).substr(2)}.payable`; - const domainHash = nameResolverTimed.namehash(domain); const randomAddress = TestUtils.getRandomAddress(); // claim domain @@ -435,9 +444,6 @@ describe('NameResolver class', function() { // check address expect(await nameResolverTimed.getAddress(domain)).to.eq(randomAddress); - // fetch old valid duration - const oldValidUntil = await executor.executeContractCall(timedEns, 'validUntil', domainHash); - // wait for 1s (must be before timeValidMs + timeValidPreExpireWindowMs) await TestUtils.sleep(1000); await TestUtils.nextBlock(executor, domainOwner); @@ -445,7 +451,9 @@ describe('NameResolver class', function() { // address is still up expect(await nameResolverTimed.getAddress(domain)).to.eq(randomAddress); // extend uptime - const extendP = nameResolverTimed.claimAddress(domain, domainOwner, domainOwner, getPrice()); + const extendP = nameResolverTimed.claimAddress( + domain, domainOwner, domainOwner, getPrice(), + ); await expect(extendP).to.be.rejected; }); @@ -546,7 +554,6 @@ describe('NameResolver class', function() { it('can have its addresses lookup expire after valid duration', async () => { const domain = `sample_${Math.random().toString(36).substr(2)}.payable`; - const domainHash = nameResolverTimed.namehash(domain); const randomAddress = TestUtils.getRandomAddress(); // claim domain @@ -557,9 +564,6 @@ describe('NameResolver class', function() { // check address expect(await nameResolverTimed.getAddress(domain)).to.eq(randomAddress); - // fetch old valid duration - const oldValidUntil = await executor.executeContractCall(timedEns, 'validUntil', domainHash); - // wait for resolval to be available (plus 1s to be sure, that we surpassed validity) await TestUtils.sleep(timeValidMs); await TestUtils.sleep(2000); @@ -582,9 +586,6 @@ describe('NameResolver class', function() { // check address expect(await nameResolverTimed.getAddress(domain)).to.eq(randomAddress); - // fetch old valid duration - const oldValidUntil = await executor.executeContractCall(timedEns, 'validUntil', domainHash); - // wait for resolval to be available (plus 1s to be sure, that we surpassed validity) await TestUtils.sleep(timeValidMs); await TestUtils.nextBlock(executor, domainOwner); @@ -607,9 +608,6 @@ describe('NameResolver class', function() { // check address expect(await nameResolverTimed.getAddress(domain)).to.eq(randomAddress); - // fetch old valid duration - const oldValidUntil = await executor.executeContractCall(timedEns, 'validUntil', domainHash); - // wait for resolval to be available (plus 1s to be sure, that we surpassed validity) await TestUtils.sleep(timeValidMs + timeValidPostExpireWindowMs); await TestUtils.sleep(2000); @@ -629,7 +627,8 @@ describe('NameResolver class', function() { expect(oldOwner).not.to.eq(randomAccount); // claim domain const claimPermantP = nameResolverTimed.claimPermanentAddress( - domain, domainOwner, randomAccount); + domain, domainOwner, randomAccount, + ); await expect(claimPermantP).to.be.rejected; }); @@ -652,7 +651,6 @@ describe('NameResolver class', function() { it('cannot set validUntil for a domain bought directly from registrar', async () => { const domain = `sample_${Math.random().toString(36).substr(2)}.payable`; - const randomAddress = TestUtils.getRandomAddress(); // get domain and subdomain await nameResolverTimed.claimAddress(domain, domainOwner, domainOwner, getPrice()); @@ -667,22 +665,22 @@ describe('NameResolver class', function() { it('cannot buy an address from the registrar from another account', async () => { const domain = `sample_${Math.random().toString(36).substr(2)}.payable`; const domainHash = nameResolverTimed.namehash(domain); - // check owner at ens - const oldOwner = await executor.executeContractCall(timedEns, 'owner', domainHash); // claim domain await nameResolverTimed.claimAddress(domain, domainOwner, domainOwner, getPrice()); // check again expect(await executor.executeContractCall( - timedEns, 'owner', domainHash)).to.eq(domainOwner); + timedEns, 'owner', domainHash, + )).to.eq(domainOwner); // try to claim with other account - const claimAddressP = nameResolverTimed.claimAddress(domain, domainNonOwner, domainNonOwner, getPrice()); + const claimAddressP = nameResolverTimed.claimAddress( + domain, domainNonOwner, domainNonOwner, getPrice(), + ); await expect(claimAddressP).to.be.rejected; }); it('cannot extend valid duration via registrar before expiration (and in extension timeframe) from another account', async () => { const domain = `sample_${Math.random().toString(36).substr(2)}.payable`; - const domainHash = nameResolverTimed.namehash(domain); const randomAddress = TestUtils.getRandomAddress(); // claim domain @@ -693,9 +691,6 @@ describe('NameResolver class', function() { // check address expect(await nameResolverTimed.getAddress(domain)).to.eq(randomAddress); - // fetch old valid duration - const oldValidUntil = await executor.executeContractCall(timedEns, 'validUntil', domainHash); - // wait for resolval to be available await TestUtils.sleep(timeValidMs + timeValidPreExpireWindowMs); await TestUtils.nextBlock(executor, domainOwner); @@ -704,15 +699,16 @@ describe('NameResolver class', function() { expect(await nameResolverTimed.getAddress(domain)).to.eq(randomAddress); // extend uptime - const claimAddressP = nameResolverTimed.claimAddress(domain, domainNonOwner, domainNonOwner, getPrice()); + const claimAddressP = nameResolverTimed.claimAddress( + domain, domainNonOwner, domainNonOwner, getPrice(), + ); await expect(claimAddressP).to.be.rejected; }); - it('cannot extend valid duration via registrar after expiration ' + - '(and before everyone can buy it) from another account', + it('cannot extend valid duration via registrar after expiration ' + + '(and before everyone can buy it) from another account', async () => { const domain = `sample_${Math.random().toString(36).substr(2)}.payable`; - const domainHash = nameResolverTimed.namehash(domain); const randomAddress = TestUtils.getRandomAddress(); // claim domain @@ -723,9 +719,6 @@ describe('NameResolver class', function() { // check address expect(await nameResolverTimed.getAddress(domain)).to.eq(randomAddress); - // fetch old valid duration - const oldValidUntil = await executor.executeContractCall(timedEns, 'validUntil', domainHash); - // wait for resolval to be available and extra owner lock has passed await TestUtils.sleep(timeValidMs); await TestUtils.sleep(2000); @@ -735,7 +728,9 @@ describe('NameResolver class', function() { expect(await nameResolverTimed.getAddress(domain)).to.eq(null); // extend uptime - const claimAddressP = nameResolverTimed.claimAddress(domain, domainNonOwner, domainNonOwner, getPrice()); + const claimAddressP = nameResolverTimed.claimAddress( + domain, domainNonOwner, domainNonOwner, getPrice(), + ); await expect(claimAddressP).to.be.rejected; }); @@ -780,7 +775,8 @@ describe('NameResolver class', function() { expect(oldOwner).not.to.eq(randomAccount); // claim domain const claimPermantP = nameResolverTimed.claimPermanentAddress( - domain, domainNonOwner, randomAccount); + domain, domainNonOwner, randomAccount, + ); await expect(claimPermantP).to.be.rejected; }); @@ -803,7 +799,6 @@ describe('NameResolver class', function() { it('cannot set validUntil for a domain bought directly from registrar', async () => { const domain = `sample_${Math.random().toString(36).substr(2)}.payable`; - const randomAddress = TestUtils.getRandomAddress(); // get domain and subdomain await nameResolverTimed.claimAddress(domain, domainOwner, domainOwner, getPrice()); @@ -826,7 +821,8 @@ describe('NameResolver class', function() { await nameResolverTimed.claimPermanentAddress(domain, ensOwner, randomAccount); // check again expect(await executor.executeContractCall( - timedEns, 'owner', domainHash)).to.eq(randomAccount); + timedEns, 'owner', domainHash, + )).to.eq(randomAccount); }); it('can set validUntil for a subdomain of an owned domain', async () => { @@ -845,7 +841,6 @@ describe('NameResolver class', function() { it('cannot set validUntil for a domain bought directly from registrar', async () => { const domain = `sample_${Math.random().toString(36).substr(2)}.payable`; - const randomAddress = TestUtils.getRandomAddress(); // get domain and subdomain await nameResolverTimed.claimAddress(domain, domainOwner, domainOwner, getPrice()); diff --git a/src/name-resolver.ts b/src/name-resolver.ts index d7494022..47fa8a16 100644 --- a/src/name-resolver.ts +++ b/src/name-resolver.ts @@ -17,11 +17,10 @@ the following URL: https://evan.network/license/ */ import * as Dbcp from '@evan.network/dbcp'; -import prottle = require('prottle'); - +// eslint-disable-next-line import/prefer-default-export export class NameResolver extends Dbcp.NameResolver { - constructor(options: any) { + public constructor(options: any) { super(options); } @@ -35,36 +34,37 @@ export class NameResolver extends Dbcp.NameResolver { * @param {string|number} value (optional) value to send (if registrar is payable) * @return {Promise} resolved when done */ - public async claimAddress( - name: string, accountId: string, domainOwnerId = accountId, value = '0'): Promise { + public async claimAddress(name: string, accountId: string, domainOwnerId = accountId, value = '0'): Promise { // check ownership const namehash = this.namehash(name); const nodeOwner = await this.executor.executeContractCall(this.ensContract, 'owner', namehash); - if (nodeOwner !== domainOwnerId && - nodeOwner !== '0x0000000000000000000000000000000000000000' && - value === '0' ) { + if (nodeOwner !== domainOwnerId + && nodeOwner !== '0x0000000000000000000000000000000000000000' + && value === '0') { // if node is owned and we don't try a payable registrar node ownership overwrite/renew - const msg = `cannot claim address "${name}", it\'s' already claimed by "${nodeOwner}"`; + const msg = `cannot claim address "${name}", it's' already claimed by "${nodeOwner}"`; this.log(msg, 'error'); throw new Error(msg); } - const [ , node, parentName ] = /^([^.]+)\.(.*)$/.exec(name); + const [, node, parentName] = /^([^.]+)\.(.*)$/.exec(name); const parentOnwer = await this.executor.executeContractCall( - this.ensContract, 'owner', this.namehash(parentName)); + this.ensContract, 'owner', this.namehash(parentName), + ); if (parentOnwer === '0x0000000000000000000000000000000000000000') { const msg = `cannot claim address "${name}", parent node is not owned`; this.log(msg, 'error'); throw new Error(msg); } if ((await this.web3.eth.getCode(parentOnwer)) === '0x') { - const msg = `cannot claim address "${name}", ` + - `parent node owner "${parentOnwer}" does not seem to be a contract`; + const msg = `cannot claim address "${name}", ` + + `parent node owner "${parentOnwer}" does not seem to be a contract`; this.log(msg, 'error'); throw new Error(msg); } const registrar = this.contractLoader.loadContract( - value ? 'PayableRegistrar' : 'FIFSRegistrar', parentOnwer); + value ? 'PayableRegistrar' : 'FIFSRegistrar', parentOnwer, + ); await this.executor.executeContractTransaction( registrar, 'register', @@ -84,24 +84,29 @@ export class NameResolver extends Dbcp.NameResolver { * @return {Promise} resolved when done */ public async claimPermanentAddress( - name: string, accountId: string, domainOwnerId = accountId): Promise { - const [ , node, parentName ] = /^([^.]+)\.(.*)$/.exec(name); + name: string, + accountId: string, + domainOwnerId = accountId, + ): Promise { + const [, node, parentName] = /^([^.]+)\.(.*)$/.exec(name); const parentOnwer = await this.executor.executeContractCall( - this.ensContract, 'owner', this.namehash(parentName)); + this.ensContract, 'owner', this.namehash(parentName), + ); if (parentOnwer === '0x0000000000000000000000000000000000000000') { const msg = `cannot claim address "${name}", parent node is not owned`; this.log(msg, 'error'); throw new Error(msg); } if ((await this.web3.eth.getCode(parentOnwer)) === '0x') { - const msg = `cannot claim address "${name}", ` + - `parent node owner "${parentOnwer}" does not seem to be a contract`; + const msg = `cannot claim address "${name}", ` + + `parent node owner "${parentOnwer}" does not seem to be a contract`; this.log(msg, 'error'); throw new Error(msg); } const registrar = this.contractLoader.loadContract('PayableRegistrar', parentOnwer); await this.executor.executeContractTransaction( - registrar, 'registerPermanent', { from: accountId }, this.soliditySha3(node), domainOwnerId); + registrar, 'registerPermanent', { from: accountId }, this.soliditySha3(node), domainOwnerId, + ); } /** @@ -115,15 +120,16 @@ export class NameResolver extends Dbcp.NameResolver { */ public async claimFunds(name: string, accountId: string): Promise { const parentOnwer = await this.executor.executeContractCall( - this.ensContract, 'owner', this.namehash(name)); + this.ensContract, 'owner', this.namehash(name), + ); if (parentOnwer === '0x0000000000000000000000000000000000000000') { const msg = `cannot claim funds for "${name}", node is not owned`; this.log(msg, 'error'); throw new Error(msg); } if ((await this.web3.eth.getCode(parentOnwer)) === '0x') { - const msg = `cannot claim funds for "${name}", ` + - `node owner "${parentOnwer}" does not seem to be a contract`; + const msg = `cannot claim funds for "${name}", ` + + `node owner "${parentOnwer}" does not seem to be a contract`; this.log(msg, 'error'); throw new Error(msg); } @@ -139,17 +145,18 @@ export class NameResolver extends Dbcp.NameResolver { * @return {Promise} price in Wei */ public async getPrice(name: string): Promise { - const [ , , parentName ] = /^([^.]+)\.(.*)$/.exec(name); + const [, , parentName] = /^([^.]+)\.(.*)$/.exec(name); const parentOnwer = await this.executor.executeContractCall( - this.ensContract, 'owner', this.namehash(parentName)); + this.ensContract, 'owner', this.namehash(parentName), + ); if (parentOnwer === '0x0000000000000000000000000000000000000000') { const msg = `cannot get price for "${name}", parent node is not owned`; this.log(msg, 'error'); throw new Error(msg); } if ((await this.web3.eth.getCode(parentOnwer)) === '0x') { - const msg = `cannot get price for "${name}", ` + - `parent node owner "${parentOnwer}" does not seem to be a contract`; + const msg = `cannot get price for "${name}", ` + + `parent node owner "${parentOnwer}" does not seem to be a contract`; this.log(msg, 'error'); throw new Error(msg); } @@ -185,15 +192,16 @@ export class NameResolver extends Dbcp.NameResolver { */ public async setPrice(name: string, accountId: string, newPrice: number|string): Promise { const parentOnwer = await this.executor.executeContractCall( - this.ensContract, 'owner', this.namehash(name)); + this.ensContract, 'owner', this.namehash(name), + ); if (parentOnwer === '0x0000000000000000000000000000000000000000') { const msg = `cannot set price for "${name}", node is not owned`; this.log(msg, 'error'); throw new Error(msg); } if ((await this.web3.eth.getCode(parentOnwer)) === '0x') { - const msg = `cannot set price for "${name}", ` + - `node owner "${parentOnwer}" does not seem to be a contract`; + const msg = `cannot set price for "${name}", ` + + `node owner "${parentOnwer}" does not seem to be a contract`; this.log(msg, 'error'); throw new Error(msg); } @@ -218,7 +226,10 @@ export class NameResolver extends Dbcp.NameResolver { * @return {Promise} resolved when done */ public async setValidUntil( - name: string, accountId: string, validUntil: number|string): Promise { + name: string, + accountId: string, + validUntil: number|string, + ): Promise { const numberString = `${validUntil}`; await this.executor.executeContractTransaction( this.ensContract, diff --git a/src/onboarding.spec.ts b/src/onboarding.spec.ts index 7cf4d576..fcdf654c 100644 --- a/src/onboarding.spec.ts +++ b/src/onboarding.spec.ts @@ -19,7 +19,7 @@ import 'mocha'; import { expect, use } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); +import * as chaiAsPromised from 'chai-as-promised'; import { accounts } from './test/accounts'; @@ -31,7 +31,7 @@ import { TestUtils } from './test/test-utils'; use(chaiAsPromised); -describe('Onboarding helper', function() { +describe('Onboarding helper', function test() { this.timeout(600000); let ipfs; let mailbox: Mailbox; @@ -46,7 +46,7 @@ describe('Onboarding helper', function() { const profile = await TestUtils.getProfile(web3, ipfs, ipld); await profile.loadForAccount(); keyProvider.init(profile); - keyProvider.currentAccount = accounts[0]; + [keyProvider.currentAccount] = accounts; mailbox = new Mailbox({ mailboxOwner: accounts[0], @@ -66,7 +66,7 @@ describe('Onboarding helper', function() { const keyExchangeOptions = { mailbox, - cryptoProvider: TestUtils.getCryptoProvider(), + cryptoProvider: TestUtils.getCryptoProvider(), defaultCryptoAlgo: 'aes', account: accounts[0], keyProvider: TestUtils.getKeyProvider(), @@ -85,16 +85,16 @@ describe('Onboarding helper', function() { accountDetails: { profileType: 'dumb dumb', accountName: 'test account', - }}); + }, + }); await expect(profilePromise) .to.be.rejectedWith('The parameters passed are incorrect, profile properties need to be reconfigured'); - }); it('should create a new random mnemonic', () => { const mnemonic = Onboarding.createMnemonic(); expect(mnemonic).to.be.an('string'); - }) + }); it('should check if an account has enough amount of eves to create new profile', async () => { const originRuntime = await TestUtils.getRuntime(accounts[0]); @@ -118,10 +118,11 @@ describe('Onboarding helper', function() { accountDetails: { profileType: 'company', accountName: 'test account', - }}); + }, + }); expect(newProfile).to.be.exist; expect(newProfile.runtimeConfig).to.be.deep.eq(runtimeConfig); - }) + }); it('should create a new profile from a different account', async () => { const originRuntime = await TestUtils.getRuntime(accounts[0]); @@ -133,7 +134,7 @@ describe('Onboarding helper', function() { accountDetails: { profileType: 'company', accountName: 'test account', - } + }, }); expect(newProfile.runtimeConfig).to.be.deep.eq(runtimeConfig); diff --git a/src/onboarding.ts b/src/onboarding.ts index 06361d47..e51ec6f0 100644 --- a/src/onboarding.ts +++ b/src/onboarding.ts @@ -17,8 +17,6 @@ the following URL: https://evan.network/license/ */ -import KeyStore = require('../libs/eth-lightwallet/keystore'); -import https = require('https'); import { cloneDeep, merge } from 'lodash'; import { @@ -31,6 +29,9 @@ import { Mail, Mailbox } from './mailbox'; import { Profile } from './profile/profile'; import * as AccountType from './profile/types/types'; +import KeyStore = require('../libs/eth-lightwallet/keystore'); +import https = require('https'); + /** * mail that will be sent to invitee */ @@ -76,23 +77,29 @@ export class Onboarding extends Logger { * @return {Promise} resolved when done */ - public static async createNewProfile(runtime: Runtime, mnemonic: string, password: string, profileProperties: any + public static async createNewProfile( + runtime: Runtime, + mnemonic: string, + password: string, + profileProperties: any, ): Promise { if (!mnemonic) { - throw new Error(`mnemonic is a required parameter!`); + throw new Error('mnemonic is a required parameter!'); } if (!password) { - throw new Error(`password is a required parameter!`); + throw new Error('password is a required parameter!'); } - const runtimeConfig: any = await Onboarding.generateRuntimeConfig(mnemonic, password, runtime.web3); + const runtimeConfig: any = await Onboarding.generateRuntimeConfig( + mnemonic, password, runtime.web3, + ); const runtimeNew = await createDefaultRuntime(runtime.web3, runtime.dfs, runtimeConfig); // check if the source runtime has enough funds const profileCost = runtime.web3.utils.toWei('1.0097'); const runtimeFunds = await runtime.web3.eth.getBalance(runtime.activeAccount); - const BN = runtime.web3.utils.BN; + const { BN } = runtime.web3.utils; if ((new BN(runtimeFunds)).lt(new BN(profileCost))) { throw new Error(`The account ${runtime.activeAccount} has less than 1.0097 EVE to create a new profile`); } @@ -110,19 +117,18 @@ export class Onboarding extends Logger { from: runtime.activeAccount, to: runtimeNew.activeAccount, value: profileCost, - }) + }); await Onboarding.createProfile(runtimeNew, profileProperties); return { mnemonic, password, - runtimeConfig - } + runtimeConfig, + }; } - /** * create new profile, store it to profile index initialize addressBook and publicKey * @@ -130,27 +136,27 @@ export class Onboarding extends Logger { * @return {Promise} resolved when done */ public static async createProfile(runtime, profileData: any): Promise { - const factoryDomain = runtime.nameResolver.getDomainName( - runtime.nameResolver.config.domains.profileFactory); + runtime.nameResolver.config.domains.profileFactory, + ); const factoryAddress = await runtime.nameResolver.getAddress(factoryDomain); const combinedDataSchema = merge( - ...Object.keys(AccountType).map(accountType => cloneDeep(AccountType[accountType]))) - const properties = combinedDataSchema.template.properties - const ajvProperties = {} - for (let key of Object.keys(properties)) { - ajvProperties[key] = properties[key].dataSchema + ...Object.keys(AccountType).map((accountType) => cloneDeep(AccountType[accountType])), + ); + const { properties } = combinedDataSchema.template; + const ajvProperties = {}; + for (const key of Object.keys(properties)) { + ajvProperties[key] = properties[key].dataSchema; } - const dataSchemaEntries = - Object.keys(ajvProperties).filter(property => ajvProperties[property].type !== 'array') - const dataSchemaLists = - Object.keys(ajvProperties).filter(property => ajvProperties[property].type === 'array') + const dataSchemaEntries = Object.keys(ajvProperties).filter((property) => ajvProperties[property].type !== 'array'); + const dataSchemaLists = Object.keys(ajvProperties).filter((property) => ajvProperties[property].type === 'array'); const description = { public: { - name: 'Container Contract (DataContract)', - description: 'Container for Digital Twin Data', + name: 'Profile Container', + description: 'Container contract for storing and sharing profile related information ' + + '(account type, company information, device detail, ...)', author: '', version: '0.1.0', dbcpVersion: 2, @@ -161,17 +167,19 @@ export class Onboarding extends Logger { }, }; const descriptionHash = await runtime.dfs.add( - 'description', Buffer.from(JSON.stringify(description), 'binary')); + 'description', Buffer.from(JSON.stringify(description), 'binary'), + ); const factory = runtime.contractLoader.loadContract( - 'ProfileDataContractFactoryInterface', factoryAddress) + 'ProfileDataContractFactoryInterface', factoryAddress, + ); const contractId = await runtime.executor.executeContractTransaction( factory, 'createContract', { from: runtime.activeAccount, autoGas: 1.1, - event: { target: 'BaseContractFactoryInterface', eventName: 'ContractCreated', }, + event: { target: 'BaseContractFactoryInterface', eventName: 'ContractCreated' }, getEventResult: (event, args) => args.newAddress, }, '0x'.padEnd(42, '0'), @@ -179,19 +187,20 @@ export class Onboarding extends Logger { descriptionHash, runtime.nameResolver.config.ensAddress, [...Object.values(runtime.profile.treeLabels), ...dataSchemaEntries] - .map(name => runtime.nameResolver.soliditySha3(name)), + .map((name) => runtime.nameResolver.soliditySha3(name)), dataSchemaLists - .map(name => runtime.nameResolver.soliditySha3(name)), + .map((name) => runtime.nameResolver.soliditySha3(name)), ); - const contractInterface = - runtime.contractLoader.loadContract('DataContractInterface', contractId); + const contractInterface = runtime.contractLoader.loadContract('DataContractInterface', contractId); const rootDomain = runtime.nameResolver.namehash( runtime.nameResolver.getDomainName( - runtime.nameResolver.config.domains.root)); + runtime.nameResolver.config.domains.root, + ), + ); await runtime.executor.executeContractTransaction( contractInterface, 'init', - { from: runtime.activeAccount, autoGas: 1.1, }, + { from: runtime.activeAccount, autoGas: 1.1 }, rootDomain, false, ); @@ -199,7 +208,8 @@ export class Onboarding extends Logger { const encodingUnencrypted = 'utf-8'; const cryptoAlgorithHashes = 'aesEcb'; const cryptorAes = runtime.cryptoProvider.getCryptorByCryptoAlgo( - 'aes'); + 'aes', + ); const hashCryptor = runtime.cryptoProvider.getCryptorByCryptoAlgo(cryptoAlgorithHashes); const [hashKey, blockNr] = await Promise.all([ hashCryptor.generateKey(), @@ -211,45 +221,48 @@ export class Onboarding extends Logger { const profileKeys = Object.keys(profileData); // add hashKey await runtime.sharing.extendSharings( - sharings, runtime.activeAccount, runtime.activeAccount, '*', 'hashKey', hashKey); + sharings, runtime.activeAccount, runtime.activeAccount, '*', 'hashKey', hashKey, + ); // extend sharings for profile data const dataContentKeys = await Promise.all(profileKeys.map(() => cryptorAes.generateKey())); - for (let i = 0; i < profileKeys.length; i++) { + for (let i = 0; i < profileKeys.length; i += 1) { await runtime.sharing.extendSharings( - sharings, runtime.activeAccount, runtime.activeAccount, profileKeys[i], blockNr, dataContentKeys[i]); + sharings, + runtime.activeAccount, + runtime.activeAccount, profileKeys[i], blockNr, dataContentKeys[i], + ); } // upload sharings - let sharingsHash = await runtime.dfs.add( - 'sharing', Buffer.from(JSON.stringify(sharings), encodingUnencrypted)); - - - + const sharingsHash = await runtime.dfs.add( + 'sharing', Buffer.from(JSON.stringify(sharings), encodingUnencrypted), + ); + // eslint-disable-next-line no-param-reassign runtime.profile.profileOwner = runtime.activeAccount; - runtime.profile.profileContract = runtime.contractLoader.loadContract('DataContract', contractId) + // eslint-disable-next-line no-param-reassign + runtime.profile.profileContract = runtime.contractLoader.loadContract('DataContract', contractId); await runtime.executor.executeContractTransaction( runtime.profile.profileContract, 'setSharing', - { from: runtime.activeAccount, autoGas: 1.1, }, - sharingsHash + { from: runtime.activeAccount, autoGas: 1.1 }, + sharingsHash, ); const dhKeys = runtime.keyExchange.getDiffieHellmanKeys(); await Promise.all([ // call `setEntry` for each pre-build entry value - ...((profileData ? Object.keys(profileData) : []) - .map(entry => - runtime.dataContract.setEntry( - runtime.profile.profileContract, - entry, - profileData[entry], - runtime.activeAccount, - ) - ) + ...((profileData ? Object.keys(profileData) : []) + .map((entry) => runtime.dataContract.setEntry( + runtime.profile.profileContract, + entry, + profileData[entry], + runtime.activeAccount, + )) ), (async () => { await runtime.profile.addContactKey( - runtime.activeAccount, 'dataKey', dhKeys.privateKey.toString('hex')); + runtime.activeAccount, 'dataKey', dhKeys.privateKey.toString('hex'), + ); await runtime.profile.addProfileKey(runtime.activeAccount, 'alias', profileData.accountDetails.accountName); await runtime.profile.addPublicKey(dhKeys.publicKey.toString('hex')); await runtime.profile.storeForAccount(runtime.profile.treeLabels.addressBook); @@ -257,21 +270,22 @@ export class Onboarding extends Logger { })(), (async () => { const profileIndexDomain = runtime.nameResolver.getDomainName( - runtime.nameResolver.config.domains.profile); + runtime.nameResolver.config.domains.profile, + ); const profileIndexAddress = await runtime.nameResolver.getAddress(profileIndexDomain); // register profile for user const profileIndexContract = runtime.contractLoader.loadContract( - 'ProfileIndexInterface', profileIndexAddress); + 'ProfileIndexInterface', profileIndexAddress, + ); await runtime.executor.executeContractTransaction( profileIndexContract, 'setMyProfile', - { from: runtime.activeAccount, autoGas: 1.1, }, + { from: runtime.activeAccount, autoGas: 1.1 }, runtime.profile.profileContract.options.address, ); - })() - ]) - + })(), + ]); } /** @@ -286,10 +300,10 @@ export class Onboarding extends Logger { const vault: any = await new Promise((res) => { KeyStore.createVault({ seedPhrase: mnemonic, - password: password, - hdPathString : 'm/45\'/62\'/13\'/7' + password, + hdPathString: 'm/45\'/62\'/13\'/7', }, (err, result) => { - res(result) + res(result); }); }); // get the derived key @@ -304,22 +318,22 @@ export class Onboarding extends Logger { const sha9Account = web3.utils.soliditySha3.apply(web3.utils.soliditySha3, [web3.utils.soliditySha3(accountId), web3.utils.soliditySha3(accountId)].sort()); - const sha3Account = web3.utils.soliditySha3(accountId) + const sha3Account = web3.utils.soliditySha3(accountId); const dataKey = web3.utils .keccak256(accountId + password) .replace(/0x/g, ''); const runtimeConfig = { accountMap: { - [accountId]: pKey + [accountId]: pKey, }, keyConfig: { [sha9Account]: dataKey, - [sha3Account]: dataKey - } + [sha3Account]: dataKey, + }, }; return runtimeConfig; - }; + } /** * creates a complete profile and emits it to the given smart agent @@ -338,37 +352,39 @@ export class Onboarding extends Logger { accountId: string, pKey: string, recaptchaToken: string, - network = 'testcore' + network = 'testcore', ) { // check for correct profile data if (!profileData || !profileData.accountDetails || !profileData.accountDetails.accountName) { throw new Error('No profile data specified or accountDetails missing'); } + // eslint-disable-next-line no-param-reassign profileData.accountDetails.profileType = profileData.accountDetails.profileType || 'user'; // fill empty container type if (!profileData.type) { + // eslint-disable-next-line no-param-reassign profileData.type = 'profile'; } // build array with allowed fields (may include duplicates) Profile.checkCorrectProfileData(profileData, profileData.accountDetails.profileType); - const profile = runtime.profile; + const { profile } = runtime; // disable pinning while profile files are being created profile.ipld.ipfs.disablePin = true; // clear hash log profile.ipld.hashLog = []; - const pk = '0x' + pKey; - const signature = runtime.web3.eth.accounts.sign('Gimme Gimme Gimme!', pk).signature; + const pk = `0x${pKey}`; + const { signature } = runtime.web3.eth.accounts.sign('Gimme Gimme Gimme!', pk); // request a new profile contract const requestedProfile = await new Promise((resolve) => { const requestProfilePayload = JSON.stringify({ - accountId: accountId, - signature: signature, - captchaToken: recaptchaToken + accountId, + signature, + captchaToken: recaptchaToken, }); const reqOptions = { @@ -378,29 +394,30 @@ export class Onboarding extends Logger { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Content-Length': requestProfilePayload.length - } + 'Content-Length': requestProfilePayload.length, + }, }; - const reqProfileReq = https.request(reqOptions, function (res) { + const reqProfileReq = https.request(reqOptions, (res) => { const chunks = []; - res.on('data', function (chunk) { + res.on('data', (chunk) => { chunks.push(chunk); }); - res.on('end', function () { + res.on('end', () => { const body = Buffer.concat(chunks); - resolve(JSON.parse(body.toString())) + resolve(JSON.parse(body.toString())); }); }); reqProfileReq.write(requestProfilePayload); reqProfileReq.end(); - }) + }); const dhKeys = runtime.keyExchange.getDiffieHellmanKeys(); await profile.addContactKey( - runtime.activeAccount, 'dataKey', dhKeys.privateKey.toString('hex')); + runtime.activeAccount, 'dataKey', dhKeys.privateKey.toString('hex'), + ); await profile.addProfileKey(runtime.activeAccount, 'alias', profileData.accountDetails.accountName); await profile.addPublicKey(dhKeys.publicKey.toString('hex')); @@ -409,9 +426,11 @@ export class Onboarding extends Logger { const fileHashes: any = {}; const cryptorAes = runtime.cryptoProvider.getCryptorByCryptoAlgo( - runtime.dataContract.options.defaultCryptoAlgo); + runtime.dataContract.options.defaultCryptoAlgo, + ); const hashCryptor = runtime.cryptoProvider.getCryptorByCryptoAlgo( - runtime.dataContract.cryptoAlgorithHashes); + runtime.dataContract.cryptoAlgorithHashes, + ); const [hashKey, blockNr] = await Promise.all([ hashCryptor.generateKey(), runtime.web3.eth.getBlockNumber(), @@ -422,67 +441,71 @@ export class Onboarding extends Logger { const profileKeys = Object.keys(profileData); // add hashKey await runtime.sharing.extendSharings( - sharings, accountId, accountId, '*', 'hashKey', hashKey); + sharings, accountId, accountId, '*', 'hashKey', hashKey, + ); // extend sharings for profile data const dataContentKeys = await Promise.all(profileKeys.map(() => cryptorAes.generateKey())); - for (let i = 0; i < profileKeys.length; i++) { + for (let i = 0; i < profileKeys.length; i += 1) { await runtime.sharing.extendSharings( - sharings, accountId, accountId, profileKeys[i], blockNr, dataContentKeys[i]); + sharings, accountId, accountId, profileKeys[i], blockNr, dataContentKeys[i], + ); } // upload sharings - let sharingsHash = await runtime.dfs.add( - 'sharing', Buffer.from(JSON.stringify(sharings), runtime.dataContract.encodingUnencrypted)); + const sharingsHash = await runtime.dfs.add( + 'sharing', Buffer.from(JSON.stringify(sharings), runtime.dataContract.encodingUnencrypted), + ); // used to exclude encrypted hashes from fileHashes.ipfsHashes - const ipfsExcludeHashes = [ ]; + const ipfsExcludeHashes = []; // encrypt profileData fileHashes.properties = { entries: { } }; await Promise.all(Object.keys(profileData).map(async (key: string, index: number) => { const encrypted = await cryptorAes.encrypt( profileData[key], - { key: dataContentKeys[index] } + { key: dataContentKeys[index] }, ); const envelope = { private: encrypted.toString('hex'), cryptoInfo: cryptorAes.getCryptoInfo( - runtime.nameResolver.soliditySha3((requestedProfile as any).contractId)), + runtime.nameResolver.soliditySha3((requestedProfile as any).contractId), + ), }; - let ipfsHash = await runtime.dfs.add(key, Buffer.from(JSON.stringify(envelope))); - profile.ipld.hashLog.push(`${ ipfsHash.toString('hex') }`); + const ipfsHash = await runtime.dfs.add(key, Buffer.from(JSON.stringify(envelope))); + profile.ipld.hashLog.push(`${ipfsHash.toString('hex')}`); fileHashes.properties.entries[key] = await cryptor.encrypt( Buffer.from(ipfsHash.substr(2), 'hex'), - { key: hashKey, } + { key: hashKey }, ); - fileHashes.properties.entries[key] = `0x${ fileHashes.properties.entries[key] - .toString('hex') }`; + fileHashes.properties.entries[key] = `0x${fileHashes.properties.entries[key] + .toString('hex')}`; ipfsExcludeHashes.push(fileHashes.properties.entries[key]); })); - fileHashes.properties.entries[profile.treeLabels.addressBook] = - await profile.storeToIpld(profile.treeLabels.addressBook); - fileHashes.properties.entries[profile.treeLabels.publicKey] = - await profile.storeToIpld(profile.treeLabels.publicKey); + fileHashes.properties.entries[profile.treeLabels.addressBook] = await profile.storeToIpld( + profile.treeLabels.addressBook, + ); + fileHashes.properties.entries[profile.treeLabels.publicKey] = await profile.storeToIpld( + profile.treeLabels.publicKey, + ); fileHashes.sharingsHash = sharingsHash; fileHashes.properties.entries[profile.treeLabels.addressBook] = await cryptor.encrypt( Buffer.from(fileHashes.properties.entries[profile.treeLabels.addressBook].substr(2), 'hex'), - { key: hashKey, } - ) - fileHashes.properties.entries[profile.treeLabels.addressBook] = - `0x${fileHashes.properties.entries[profile.treeLabels.addressBook].toString('hex')}`; + { key: hashKey }, + ); + fileHashes.properties.entries[profile.treeLabels.addressBook] = `0x${fileHashes.properties.entries[profile.treeLabels.addressBook].toString('hex')}`; // keep only unique values, ignore addressbook (encrypted hash) fileHashes.ipfsHashes = [ ...profile.ipld.hashLog, ...Object.keys(fileHashes.properties.entries) - .map(key => fileHashes.properties.entries[key]), + .map((key) => fileHashes.properties.entries[key]), ]; fileHashes.ipfsHashes = ( (arrArg) => arrArg.filter( - (elem, pos, arr) => - arr.indexOf(elem) === pos && - (elem !== fileHashes.properties.entries[profile.treeLabels.addressBook] && - ipfsExcludeHashes.indexOf(elem) === -1) + (elem, pos, arr) => arr.indexOf(elem) === pos + && (elem !== fileHashes.properties.entries[profile.treeLabels.addressBook] + && ipfsExcludeHashes.indexOf(elem) === -1), ) )(fileHashes.ipfsHashes); // clear hash log @@ -491,12 +514,12 @@ export class Onboarding extends Logger { profile.ipld.ipfs.disablePin = false; const data = JSON.stringify({ - accountId: accountId, - signature: signature, + accountId, + signature, profileInfo: fileHashes, accessToken: (requestedProfile as any).accessToken, contractId: (requestedProfile as any).contractId, - }) + }); const options = { hostname: `agents${network === 'testcore' ? '.test' : ''}.evan.network`, port: 443, @@ -504,23 +527,23 @@ export class Onboarding extends Logger { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Content-Length': data.length - } - } + 'Content-Length': data.length, + }, + }; - await new Promise(async (resolve, reject) => { + await new Promise((resolve, reject) => { const req = https.request(options, (res) => { res.on('data', () => { - resolve() - }) - }) + resolve(); + }); + }); req.on('error', (error) => { - reject(error) - }) + reject(error); + }); - req.write(data) - req.end() + req.write(data); + req.end(); }); } @@ -545,12 +568,13 @@ export class Onboarding extends Logger { attachments: [{ type: 'onboardingEmail', data: JSON.stringify(invitation), - }] - } + }], + }, }; // send mail to smart agent await this.options.mailbox.sendMail( - mail, this.options.mailbox.mailboxOwner, this.options.smartAgentId, weiToSend); + mail, this.options.mailbox.mailboxOwner, this.options.smartAgentId, weiToSend, + ); } } diff --git a/src/payments.spec.ts b/src/payments.spec.ts index c5effb80..6193ffad 100644 --- a/src/payments.spec.ts +++ b/src/payments.spec.ts @@ -20,37 +20,24 @@ import 'mocha'; import { expect } from 'chai'; -import { KeyProvider } from '@evan.network/dbcp'; - +import * as BigNumber from 'bignumber.js'; import { accounts } from './test/accounts'; -import { configTestcore as config } from './config-testcore'; -import { KeyExchange } from './keyExchange'; -import { Ipfs } from './dfs/ipfs'; -import { Ipld } from './dfs/ipld'; -import { InvitationMail, Onboarding } from './onboarding'; -import { Mailbox } from './mailbox'; -import { Profile } from './profile/profile'; import { TestUtils } from './test/test-utils'; -import * as BigNumber from 'bignumber.js'; - -describe('Payment Channels', function() { +describe('Payment Channels', function test() { this.timeout(600000); - let nameResolver; let payments1; let payments2; let executor; let web3; let initialChannel; let proof; - let accountStore; before(async () => { web3 = TestUtils.getWeb3(); executor = await TestUtils.getExecutor(web3); - payments1 = await TestUtils.getPayments(web3, accounts[0]); - payments2 = await TestUtils.getPayments(web3, accounts[1]); - accountStore = await TestUtils.getAccountStore({}); + payments1 = await TestUtils.getPayments(web3); + payments2 = await TestUtils.getPayments(web3); }); @@ -58,7 +45,7 @@ describe('Payment Channels', function() { const channelManager = await executor.createContract( 'RaidenMicroTransferChannels', [500, []], - { from: accounts[0], gas: 3000000, } + { from: accounts[0], gas: 3000000 }, ); payments1.setChannelManager(channelManager.options.address); payments2.setChannelManager(channelManager.options.address); @@ -94,7 +81,7 @@ describe('Payment Channels', function() { const balanceReceiverBefore = new BigNumber(await web3.eth.getBalance(accounts[1])); await payments2.closeChannel(closingSig); - let result = await payments2.getChannelInfo(); + const result = await payments2.getChannelInfo(); expect(result).to.have.all.keys('state', 'block', 'deposit', 'withdrawn'); expect(result.state).to.be.equal('settled'); expect(result.deposit.eq(new BigNumber(0))).to.be.true; @@ -116,7 +103,7 @@ describe('Payment Channels', function() { await payments1.closeChannel(closingSig); - let result = await payments1.getChannelInfo(); + const result = await payments1.getChannelInfo(); expect(result).to.have.all.keys('state', 'block', 'deposit', 'withdrawn'); expect(result.state).to.be.equal('settled'); expect(result.deposit.eq(new BigNumber(0))).to.be.true; @@ -125,7 +112,7 @@ describe('Payment Channels', function() { expect(balanceReceiverAfter.eq(balanceReceiverBefore.add(1))); }); - it.skip('should close a channel un-cooperative from the receiver side', async () => { + it.skip('should close a channel un-cooperative from the receiver side', async () => { const closingSig = await payments2.getClosingSig(accounts[1]); await payments2.closeChannel(closingSig); }); diff --git a/src/payments.ts b/src/payments.ts index d8d24586..3f06f2e7 100644 --- a/src/payments.ts +++ b/src/payments.ts @@ -26,16 +26,20 @@ import { } from '@evan.network/dbcp'; import * as BigNumber from 'bignumber.js'; -import { typedSignatureHash, signTypedDataLegacy, recoverTypedSignatureLegacy, concatSig } from 'eth-sig-util'; +import { + recoverTypedSignatureLegacy, + signTypedDataLegacy, + typedSignatureHash, +} from 'eth-sig-util'; /** * parameters for Payments constructor */ export interface PaymentOptions extends LoggerOptions { - executor: any, - accountStore: AccountStore, - contractLoader: ContractLoader, - web3: any, + executor: any; + accountStore: AccountStore; + contractLoader: ContractLoader; + web3: any; } /** @@ -122,27 +126,28 @@ interface MsgParam { * @class Payments (name) */ export class Payments extends Logger { - options: PaymentOptions; + public options: PaymentOptions; + /** * Currently set channel info. May be loaded through [[loadStoredChannel]], * [[loadChannelFromBlockchain]], or stored and set manually with [[setChannel]] */ - channel: MicroChannel; - channelManager: any; - startBlock: any; - challenge: any; + public channel: MicroChannel; + + public channelManager: any; + + public startBlock: any; - constructor(options) { + public challenge: any; + + public constructor(options) { super(options); this.options = options; this.startBlock = 0; - if (concatSig) { - const sig = concatSig; - } if (options.channelManager) { this.channelManager = this.options.contractLoader.loadContract( 'RaidenMicroTransferChannels', - options.channelManager + options.channelManager, ); } } @@ -159,7 +164,7 @@ export class Payments extends Logger { * @param closingSig Cooperative-close signature from receiver * @returns Promise to closing tx hash */ - async closeChannel(closingSig?: string): Promise { + public async closeChannel(closingSig?: string): Promise { if (!this.isChannelValid()) { throw new Error('No valid channelInfo'); } @@ -169,13 +174,14 @@ export class Payments extends Logger { } if (this.channel.closing_sig) { + // eslint-disable-next-line no-param-reassign closingSig = this.channel.closing_sig; } else if (closingSig) { - this.setChannel(Object.assign( - {}, - this.channel, - { closing_sig: closingSig }, - )); + this.setChannel({ + + ...this.channel, + closing_sig: closingSig, // eslint-disable-line @typescript-eslint/camelcase + }); } this.log(`Closing channel. Cooperative = ${closingSig}`, 'debug'); @@ -187,7 +193,7 @@ export class Payments extends Logger { proof = this.channel.proof; } - closingSig ? + if (closingSig) { await this.options.executor.executeContractTransaction( this.channelManager, 'cooperativeClose', @@ -197,7 +203,8 @@ export class Payments extends Logger { this.options.web3.utils.toHex(proof.balance), proof.sig, closingSig, - ) : + ); + } else { await this.options.executor.executeContractTransaction( this.channelManager, 'uncooperativeClose', @@ -206,6 +213,7 @@ export class Payments extends Logger { this.channel.block, this.options.web3.utils.toHex(proof.balance), ); + } } /** @@ -215,17 +223,17 @@ export class Payments extends Logger { * or right after signNewProof is resolved, * if implementation don't care for request status */ - confirmPayment(proof: MicroProof): void { + public confirmPayment(proof: MicroProof): void { if (!this.channel.next_proof || !this.channel.next_proof.sig || this.channel.next_proof.sig !== proof.sig) { throw new Error('Invalid provided or stored next signature'); } - const channel = Object.assign( - {}, - this.channel, - { proof: this.channel.next_proof }, - ); + const channel = { + + ...this.channel, + proof: this.channel.next_proof, + }; delete channel.next_proof; this.setChannel(channel); } @@ -237,8 +245,9 @@ export class Payments extends Logger { * @param channel Channel to get info from. Default to channel * @returns Promise to MicroChannelInfo data */ - async getChannelInfo(channel?: MicroChannel): Promise { + public async getChannelInfo(channel?: MicroChannel): Promise { if (!channel) { + // eslint-disable-next-line no-param-reassign channel = this.channel; } if (!this.isChannelValid(channel)) { @@ -247,12 +256,12 @@ export class Payments extends Logger { const closeEvents = await this.channelManager.getPastEvents('ChannelCloseRequested', { filter: { - _sender_address: channel.account, - _receiver_address: channel.receiver, - _open_block_number: channel.block, + _sender_address: channel.account, // eslint-disable-line @typescript-eslint/camelcase + _receiver_address: channel.receiver, // eslint-disable-line @typescript-eslint/camelcase + _open_block_number: channel.block, // eslint-disable-line @typescript-eslint/camelcase }, fromBlock: channel.block, - toBlock: 'latest' + toBlock: 'latest', }); let closed: number; @@ -264,12 +273,12 @@ export class Payments extends Logger { const settleEvents = await this.channelManager.getPastEvents('ChannelSettled', { filter: { - _sender_address: channel.account, - _receiver_address: channel.receiver, - _open_block_number: channel.block, + _sender_address: channel.account, // eslint-disable-line @typescript-eslint/camelcase + _receiver_address: channel.receiver, // eslint-disable-line @typescript-eslint/camelcase + _open_block_number: channel.block, // eslint-disable-line @typescript-eslint/camelcase }, fromBlock: closed || channel.block, - toBlock: 'latest' + toBlock: 'latest', }); let settled: number; @@ -281,10 +290,10 @@ export class Payments extends Logger { // for settled channel, getChannelInfo call will fail, so we return before if (settled) { return { - 'state': 'settled', - 'block': settled, - 'deposit': new BigNumber(0), - 'withdrawn': new BigNumber(0), + state: 'settled', + block: settled, + deposit: new BigNumber(0), + withdrawn: new BigNumber(0), }; } @@ -293,17 +302,17 @@ export class Payments extends Logger { 'getChannelInfo', channel.account, channel.receiver, - channel.block + channel.block, ); if (!(new BigNumber(info[1]).gt(0))) { - throw new Error('Invalid channel deposit: ' + JSON.stringify(info)); + throw new Error(`Invalid channel deposit: ${JSON.stringify(info)}`); } return { - 'state': closed ? 'closed' : 'opened', - 'block': closed || channel.block, - 'deposit': new BigNumber(info[1]), - 'withdrawn': new BigNumber(info[4]), + state: closed ? 'closed' : 'opened', + block: closed || channel.block, + deposit: new BigNumber(info[1]), + withdrawn: new BigNumber(info[4]), }; } @@ -315,10 +324,10 @@ export class Payments extends Logger { * * @returns Promise to challenge period number, in blocks */ - async getChallengePeriod(): Promise { + public async getChallengePeriod(): Promise { this.challenge = await this.options.executor.executeContractCall( this.channelManager, - 'challenge_period' + 'challenge_period', ); if (!(this.challenge > 0)) { throw new Error('Invalid challenge'); @@ -341,7 +350,7 @@ export class Payments extends Logger { * @param proof Balance proof to be signed * @returns Promise to signature */ - async getClosingSig(account: string): Promise { + public async getClosingSig(account: string): Promise { if (!this.isChannelValid()) { throw new Error('No valid channelInfo'); } @@ -352,7 +361,7 @@ export class Payments extends Logger { try { const result = await signTypedDataLegacy( Buffer.from(privKey, 'hex'), - { data: params } + { data: params }, ); if (result.error) { @@ -364,7 +373,9 @@ export class Payments extends Logger { throw err; } } - const recovered = this.options.web3.utils.toChecksumAddress(recoverTypedSignatureLegacy({ data: params, sig })); + const recovered = this.options.web3.utils.toChecksumAddress( + recoverTypedSignatureLegacy({ data: params, sig }), + ); this.log(`signTypedData = ${sig} , ${recovered}`, 'debug'); if (recovered !== account) { throw new Error(`Invalid recovered signature: ${recovered} != ${account}. Do your provider support eth_signTypedData?`); @@ -379,8 +390,9 @@ export class Payments extends Logger { * @param channel Channel to test. Default to channel * @returns True if channel is valid, false otherwise */ - isChannelValid(channel?: MicroChannel): boolean { + public isChannelValid(channel?: MicroChannel): boolean { if (!channel) { + // eslint-disable-next-line no-param-reassign channel = this.channel; } if (!channel || !channel.receiver || !channel.block @@ -399,8 +411,9 @@ export class Payments extends Logger { * @param amount Amount to increment in current balance * @returns Promise to signature */ - async incrementBalanceAndSign(amount: BigNumber|string): Promise { + public async incrementBalanceAndSign(amount: BigNumber|string): Promise { if (!(amount instanceof BigNumber)) { + // eslint-disable-next-line no-param-reassign amount = new BigNumber(amount); } if (!this.isChannelValid()) { @@ -413,12 +426,12 @@ export class Payments extends Logger { throw new Error('Tried signing on closed channel'); } else if (proof.balance.gt(info.deposit)) { const err = new Error(`Insuficient funds: current = ${info.deposit} , required = ${proof.balance}`); - err['current'] = info.deposit; - err['required'] = proof.balance; + (err as any).current = info.deposit; + (err as any).required = proof.balance; throw err; } // get hash for new balance proof - return await this.signNewProof(proof); + return this.signNewProof(proof); } @@ -434,58 +447,61 @@ export class Payments extends Logger { * @param receiver Receiver/server's account address * @returns Promise to channel info, if a channel was found */ - async loadChannelFromBlockchain(account: string, receiver: string): Promise { + public async loadChannelFromBlockchain(account: string, receiver: string): Promise { const openEvents = await this.channelManager.getPastEvents('ChannelCreated', { filter: { - _sender_address: account, - _receiver_address: receiver, + _sender_address: account, // eslint-disable-line @typescript-eslint/camelcase + _receiver_address: receiver, // eslint-disable-line @typescript-eslint/camelcase }, fromBlock: this.startBlock, - toBlock: 'latest' + toBlock: 'latest', }); if (!openEvents || openEvents.length === 0) { throw new Error('No channel found for this account'); } const minBlock = Math.min.apply(null, openEvents.map((ev) => ev.blockNumber)); - const [ closeEvents, settleEvents, currentBlock, challenge ] = await Promise.all([ + const [closeEvents, settleEvents, currentBlock, challenge] = await Promise.all([ this.channelManager.getPastEvents('ChannelCloseRequested', { filter: { - _sender_address: account, - _receiver_address: receiver, + _sender_address: account, // eslint-disable-line @typescript-eslint/camelcase + _receiver_address: receiver, // eslint-disable-line @typescript-eslint/camelcase }, fromBlock: minBlock, - toBlock: 'latest' + toBlock: 'latest', }), this.channelManager.getPastEvents('ChannelSettled', { filter: { - _sender_address: account, - _receiver_address: receiver, + _sender_address: account, // eslint-disable-line @typescript-eslint/camelcase + _receiver_address: receiver, // eslint-disable-line @typescript-eslint/camelcase }, fromBlock: minBlock, - toBlock: 'latest' + toBlock: 'latest', }), this.options.web3.eth.getBlockNumber(), this.getChallengePeriod(), ]); const stillOpen = openEvents.filter((ev) => { - for (let sev of settleEvents) { + for (const sev of settleEvents) { + // eslint-disable-next-line if (sev.args._open_block_number.eq(ev.blockNumber)) { return false; } } - for (let cev of closeEvents) { - if (cev.args._open_block_number.eq(ev.blockNumber) && - cev.blockNumber + challenge > currentBlock) {} + for (const cev of closeEvents) { + // eslint-disable-next-line + if (cev.args._open_block_number.eq(ev.blockNumber) + && cev.blockNumber + challenge > currentBlock) { return false; + } } return true; }); let openChannel: MicroChannel; - for (let ev of stillOpen) { - let channel: MicroChannel = { + for (const ev of stillOpen) { + const channel: MicroChannel = { account, receiver, block: ev.blockNumber, @@ -497,11 +513,12 @@ export class Payments extends Logger { break; } catch (err) { this.log(`Invalid channel ${channel}, ${err}`, 'error'); + // eslint-disable-next-line continue; } } if (!openChannel) { - throw new Error('No open and valid channels found from ' + stillOpen.length); + throw new Error(`No open and valid channels found from ${stillOpen.length}`); } this.setChannel(openChannel); return this.channel; @@ -517,8 +534,13 @@ export class Payments extends Logger { * @param deposit Tokens to be initially deposited in the channel (in Wei) * @returns Promise to MicroChannel info object */ - async openChannel(account: string, receiver: string, deposit: BigNumber|string): Promise { + public async openChannel( + account: string, + receiver: string, + deposit: BigNumber|string, + ): Promise { if (!(deposit instanceof BigNumber)) { + // eslint-disable-next-line no-param-reassign deposit = new BigNumber(deposit); } if (this.isChannelValid()) { @@ -534,18 +556,18 @@ export class Payments extends Logger { } // call transfer to make the deposit, automatic support for ERC20/223 token - let transferTxHash: string; const createdBlockNumber = await this.options.executor.executeContractTransaction( this.channelManager, 'createChannel', { from: account, value: deposit, - // event ChannelCreated(address _sender_address, address _receiver_address, uint256 _deposit) + // event ChannelCreated(address _sender_address, + // address _receiver_address, uint256 _deposit) event: { target: 'RaidenMicroTransferChannels', eventName: 'ChannelCreated' }, - getEventResult: (event, args) => event.blockNumber, + getEventResult: (event) => event.blockNumber, }, - receiver + receiver, ); // call getChannelInfo to be sure channel was created const info = await this.options.executor.executeContractCall( @@ -553,7 +575,7 @@ export class Payments extends Logger { 'getChannelInfo', account, receiver, - createdBlockNumber + createdBlockNumber, ); if (!(info[1] > 0)) { throw new Error('No deposit found!'); @@ -574,10 +596,10 @@ export class Payments extends Logger { * * @param {string} channelManager the new channelmanager address */ - setChannelManager(channelManager: string) { + public setChannelManager(channelManager: string) { this.channelManager = this.options.contractLoader.loadContract( 'RaidenMicroTransferChannels', - channelManager + channelManager, ); } @@ -588,11 +610,11 @@ export class Payments extends Logger { * * @param channel Channel info to be set */ - setChannel(channel: MicroChannel): void { + public setChannel(channel: MicroChannel): void { this.channel = channel; - if (typeof (global).localStorage !== 'undefined') { + if (typeof (global as any).localStorage !== 'undefined') { const key = [this.channel.account, this.channel.receiver].join('|'); - (global).localStorage.setItem(key, JSON.stringify(this.channel)); + (global as any).localStorage.setItem(key, JSON.stringify(this.channel)); } } @@ -604,13 +626,13 @@ export class Payments extends Logger { * * @returns Promise resolved when done */ - async settleChannel(): Promise { + public async settleChannel(): Promise { if (!this.isChannelValid()) { throw new Error('No valid channelInfo'); } - const [ info, currentBlock ] = await Promise.all([ + const [info, currentBlock] = await Promise.all([ this.getChannelInfo(), - await this.options.web3.eth.getBlockNumber() + await this.options.web3.eth.getBlockNumber(), ]); if (info.state !== 'closed') { throw new Error(`Tried settling opened or settled channel: ${info.state}`); @@ -641,12 +663,13 @@ export class Payments extends Logger { * @param proof Balance proof to be signed * @returns Promise to signature */ - async signNewProof(proof?: MicroProof): Promise { + public async signNewProof(proof?: MicroProof): Promise { if (!this.isChannelValid()) { throw new Error('No valid channelInfo'); } this.log(`signNewProof, balance: ${proof.balance.toString()}, sig: ${proof.sig}`, 'debug'); if (!proof) { + // eslint-disable-next-line no-param-reassign proof = this.channel.proof; } if (proof.sig) { @@ -655,11 +678,11 @@ export class Payments extends Logger { const params = this.getBalanceProofSignatureParams(proof); let sig: string; - const privKey = await this.options.accountStore.getPrivateKey(this.channel.account) + const privKey = await this.options.accountStore.getPrivateKey(this.channel.account); try { const result = await signTypedDataLegacy( Buffer.from(privKey, 'hex'), - { data: params } + { data: params }, ); if (result.error) { @@ -675,28 +698,31 @@ export class Payments extends Logger { // ask for signing of the hash sig = await this.signMessage(hash); } - const recovered = this.options.web3.utils.toChecksumAddress(recoverTypedSignatureLegacy({ data: params, sig })); + const recovered = this.options.web3.utils.toChecksumAddress(recoverTypedSignatureLegacy( + { data: params, sig }, + )); this.log(`signTypedData = ${sig}, ${recovered}`, 'debug'); if (recovered !== this.channel.account) { - throw new Error(`Invalid recovered signature: ${recovered} != ${this.channel.account}. ` + - 'Does your provider support eth_signTypedData?'); + throw new Error(`Invalid recovered signature: ${recovered} != ${this.channel.account}. ` + + 'Does your provider support eth_signTypedData?'); } - + // eslint-disable-next-line no-param-reassign proof.sig = sig; // return signed message if (proof.balance.equals(this.channel.proof.balance)) { - this.setChannel(Object.assign( - {}, - this.channel, - { proof, next_proof: proof } - )); + this.setChannel({ + + ...this.channel, + proof, + next_proof: proof, // eslint-disable-line @typescript-eslint/camelcase + }); } else { - this.setChannel(Object.assign( - {}, - this.channel, - { next_proof: proof } - )); + this.setChannel({ + + ...this.channel, + next_proof: proof, // eslint-disable-line @typescript-eslint/camelcase + }); } return proof; } @@ -707,38 +733,29 @@ export class Payments extends Logger { * @param msg Data to be signed * @returns Promise to signature */ - async signMessage(msg: string): Promise { + public async signMessage(msg: string): Promise { if (!this.isChannelValid()) { throw new Error('No valid channelInfo'); } const hex = msg.startsWith('0x') ? msg : this.options.web3.utils.toHex(msg); this.log(`Signing "${msg}" => ${hex}, account: ${this.channel.account}`, 'debug'); - const privKey = await this.options.accountStore.getPrivateKey(this.channel.account) - let sig = await this.options.web3.eth.accounts.sign( + const privKey = await this.options.accountStore.getPrivateKey(this.channel.account); + const sig = await this.options.web3.eth.accounts.sign( hex, - Buffer.from('0x' + privKey, 'hex') + Buffer.from(`0x${privKey}`, 'hex'), ); return sig; } - - /** - * Top up current channel, by depositing some [more] tokens to it - * - * Should work with both ERC20/ERC223 tokens - * - * @param deposit Tokens to be deposited in the channel - * @returns Promise to tx hash - */ - /** * Top up current channel, by depositing some [more] EVE to it * * @param {BigNumber|string} deposit amount to topup channel * @return {Promise} resolved when done */ - async topUpChannel(deposit: BigNumber|string): Promise { + public async topUpChannel(deposit: BigNumber|string): Promise { if (!(deposit instanceof BigNumber)) { + // eslint-disable-next-line no-param-reassign deposit = new BigNumber(deposit); } @@ -746,7 +763,7 @@ export class Payments extends Logger { throw new Error('No valid channelInfo'); } - const account = this.channel.account; + const { account } = this.channel; // first, check if there's enough balance const balance = new BigNumber(await this.options.web3.eth.getBalance(account)); @@ -794,7 +811,7 @@ export class Payments extends Logger { { type: 'uint32', name: 'block_created', - value: '' + this.channel.block, + value: `${this.channel.block}`, }, { type: 'uint256', @@ -824,7 +841,7 @@ export class Payments extends Logger { { type: 'uint32', name: 'block_created', - value: '' + this.channel.block, + value: `${this.channel.block}`, }, { type: 'uint256', @@ -838,5 +855,4 @@ export class Payments extends Logger { }, ]; } - } diff --git a/src/profile/business-center-profile.spec.ts b/src/profile/business-center-profile.spec.ts index 257a50ef..85bfc136 100644 --- a/src/profile/business-center-profile.spec.ts +++ b/src/profile/business-center-profile.spec.ts @@ -20,30 +20,23 @@ import 'mocha'; import { expect } from 'chai'; +import { TestUtils } from '../test/test-utils'; +import { accounts, accountMap } from '../test/accounts'; +import { configTestcore as config } from '../config-testcore'; import { + BusinessCenterProfile, + Ipld, + KeyProvider, NameResolver, - SignerInternal, - KeyProvider -} from '@evan.network/dbcp'; - -import { accounts } from '../test/accounts'; -import { accountMap } from '../test/accounts'; -import { Aes } from '../encryption/aes'; -import { BusinessCenterProfile } from './business-center-profile'; -import { configTestcore as config } from '../config-testcore'; -import { CryptoProvider } from '../encryption/crypto-provider'; -import { Ipld } from '../dfs/ipld'; -import { TestUtils } from '../test/test-utils'; +} from '../index'; -describe('BusinessCenterProfile helper', function() { +describe('BusinessCenterProfile helper', function test() { this.timeout(600000); let ipld: Ipld; let nameResolver: NameResolver; - let ensName; let businessCenterDomain; let web3; - let cryptoProvider = TestUtils.getCryptoProvider(); const sampleProfile = { alias: 'fnord', contact: 'fnord@contoso.com', @@ -54,32 +47,33 @@ describe('BusinessCenterProfile helper', function() { ipld = await TestUtils.getIpld(); nameResolver = await TestUtils.getNameResolver(web3); - ensName = nameResolver.getDomainName(config.nameResolver.domains.profile); businessCenterDomain = nameResolver.getDomainName(config.nameResolver.domains.businessCenter); nameResolver = await TestUtils.getNameResolver(web3); const loader = await TestUtils.getContractLoader(web3); const bcAddress = await nameResolver.getAddress(businessCenterDomain); const businessCenter = loader.loadContract('BusinessCenter', bcAddress); const executor = await TestUtils.getExecutor(web3); - let isMember = await executor.executeContractCall( - businessCenter, 'isMember', accounts[0], { from: accounts[0], gas: 3000000, }); + const isMember = await executor.executeContractCall( + businessCenter, 'isMember', accounts[0], { from: accounts[0], gas: 3000000 }, + ); if (!isMember) { await executor.executeContractTransaction( - businessCenter, 'join', { from: accounts[0], autoGas: 1.1, }); + businessCenter, 'join', { from: accounts[0], autoGas: 1.1 }, + ); } }); it('should be able to set and load a profile for a given user in a business center', async () => { // use own key for test - (ipld.keyProvider).keys[nameResolver.soliditySha3(businessCenterDomain)] = - (ipld.keyProvider).keys[nameResolver.soliditySha3(accounts[0])]; + // eslint-disable-next-line + (ipld.keyProvider as KeyProvider).keys[nameResolver.soliditySha3(businessCenterDomain)] = (ipld.keyProvider as KeyProvider).keys[nameResolver.soliditySha3(accounts[0])]; // create profile const profile = new BusinessCenterProfile({ ipld, nameResolver, defaultCryptoAlgo: 'aes', bcAddress: businessCenterDomain, - cryptoProvider: TestUtils.getCryptoProvider() + cryptoProvider: TestUtils.getCryptoProvider(), }); await profile.setContactCard(JSON.parse(JSON.stringify(sampleProfile))); @@ -93,7 +87,7 @@ describe('BusinessCenterProfile helper', function() { nameResolver, defaultCryptoAlgo: 'aes', bcAddress: businessCenterDomain, - cryptoProvider: TestUtils.getCryptoProvider() + cryptoProvider: TestUtils.getCryptoProvider(), }); await newProfile.loadForBusinessCenter(businessCenterDomain, from); diff --git a/src/profile/business-center-profile.ts b/src/profile/business-center-profile.ts index 047daace..8a45058b 100644 --- a/src/profile/business-center-profile.ts +++ b/src/profile/business-center-profile.ts @@ -18,81 +18,46 @@ */ import { + CryptoProvider, + Ipld, Logger, LoggerOptions, NameResolver, -} from '@evan.network/dbcp'; - -import { CryptoProvider } from '../encryption/crypto-provider'; -import { Ipld } from '../dfs/ipld'; - -function saveGet(root, labels) { - const split = labels.split('/'); - let pointer = root; - for (let i = 0; i < split.length; i++) { - let sublabel = split[i]; - if (pointer.hasOwnProperty(sublabel)) { - pointer = pointer[sublabel]; - } else { - pointer = undefined; - break; - } - } - return pointer; -} - - -function saveSet(root, labels, child) { - const split = labels.split('/'); - let pointer = root; - for (let i = 0; i < split.length; i++) { - let sublabel = split[i]; - if (i === split.length - 1) { - pointer[sublabel] = child; - } else if (!pointer[sublabel]) { - pointer[sublabel] = {}; - } - pointer = pointer[sublabel]; - } -} +} from '../index'; /** * parameters for Profile constructor */ -export interface ProfileOptions extends LoggerOptions { +export interface BusinessCenterProfileOptions extends LoggerOptions { + bcAddress: string; + cryptoProvider: CryptoProvider; + defaultCryptoAlgo: string; ipld: Ipld; nameResolver: NameResolver; - defaultCryptoAlgo: string; - cryptoProvider: CryptoProvider - bcAddress: string; ipldData?: any; } -export interface DappBookmark { - title: string; - description: string; - img: string; - primaryColor: string; - secondaryColor?: string; -} - - /** * profile helper class, builds profile graphs * * @class Profile (name) */ export class BusinessCenterProfile extends Logger { - ipldData: any; - ipld: Ipld; - nameResolver: NameResolver; - defaultCryptoAlgo: string; - cryptoProvider: CryptoProvider; - bcAddress: string; + public bcAddress: string; + + public cryptoProvider: CryptoProvider; + + public defaultCryptoAlgo: string; + + public ipld: Ipld; + + public ipldData: any; - constructor(options: ProfileOptions) { + public nameResolver: NameResolver; + + public constructor(options: BusinessCenterProfileOptions) { super(options); this.ipld = options.ipld; this.ipldData = options.ipldData || {}; @@ -107,7 +72,7 @@ export class BusinessCenterProfile extends Logger { * * @return {string} hash of the ipfs file */ - async storeToIpld(): Promise { + public async storeToIpld(): Promise { const stored = await this.ipld.store(this.ipldData); return stored; } @@ -118,14 +83,14 @@ export class BusinessCenterProfile extends Logger { * @param {string} ipldIpfsHash ipfs file hash that points to a file with ipld a hash * @return {Profile} this profile */ - async loadFromIpld(ipldIpfsHash: string): Promise { + public async loadFromIpld(ipldIpfsHash: string): Promise { let loaded; try { - loaded = await this.ipld.getLinkedGraph(ipldIpfsHash); + loaded = await this.ipld.getLinkedGraph(ipldIpfsHash); } catch (e) { this.log(`Error getting BC Profile ${e}`, 'debug'); loaded = { - alias: {} + alias: {}, }; } this.ipldData = loaded; @@ -137,7 +102,7 @@ export class BusinessCenterProfile extends Logger { * * @return {any} contact card */ - async getContactCard(): Promise { + public async getContactCard(): Promise { return this.ipld.getLinkedGraph(this.ipldData, 'contactCard'); } @@ -147,7 +112,7 @@ export class BusinessCenterProfile extends Logger { * @param {any} contactCard contact card to store * @return {any} updated tree */ - async setContactCard(contactCard: any): Promise { + public async setContactCard(contactCard: any): Promise { const cryptor = this.cryptoProvider.getCryptorByCryptoAlgo(this.defaultCryptoAlgo); const cryptoInfo = cryptor.getCryptoInfo(this.nameResolver.soliditySha3(this.bcAddress)); return this.ipld.set(this.ipldData, 'contactCard', contactCard, false, cryptoInfo); @@ -160,16 +125,21 @@ export class BusinessCenterProfile extends Logger { * @param {string} account Ethereum account id * @return {Promise} resolved when done */ - async storeForBusinessCenter(businessCenterDomain: string, account: string): Promise { + public async storeForBusinessCenter( + businessCenterDomain: string, + account: string, + ): Promise { const [stored, address] = await Promise.all([ this.storeToIpld(), this.nameResolver.getAddress(businessCenterDomain), ]); - const contract = this.nameResolver.contractLoader.loadContract('BusinessCenterInterface', address); + const contract = this.nameResolver.contractLoader.loadContract( + 'BusinessCenterInterface', address, + ); await this.nameResolver.executor.executeContractTransaction( contract, 'setMyProfile', - { from: account, autoGas: 1.1, }, + { from: account, autoGas: 1.1 }, stored, ); } @@ -181,18 +151,22 @@ export class BusinessCenterProfile extends Logger { * @param {string} account Ethereum account id * @return {Promise} resolved when done */ - async loadForBusinessCenter(businessCenterDomain: string, account: string): Promise { + // eslint-disable-next-line consistent-return + public async loadForBusinessCenter(businessCenterDomain: string, account: string): Promise { const address = await this.nameResolver.getAddress(businessCenterDomain); - const contract = this.nameResolver.contractLoader.loadContract('BusinessCenterInterface', address); - const hash = await this.nameResolver.executor.executeContractCall(contract, 'getProfile', account); + const contract = this.nameResolver.contractLoader.loadContract( + 'BusinessCenterInterface', address, + ); + const hash = await this.nameResolver.executor.executeContractCall( + contract, 'getProfile', account, + ); if (hash === '0x0000000000000000000000000000000000000000000000000000000000000000') { this.ipldData = { alias: '', }; return Promise.resolve(); - } else { - await this.loadFromIpld(hash); } + await this.loadFromIpld(hash); } /** @@ -203,12 +177,24 @@ export class BusinessCenterProfile extends Logger { * @param {string} account current accountId * @return {Array} Array with all registered bc contracts */ - async getMyBusinessCenterContracts(businessCenterDomain: string, contractType: string, account: string): Promise { + public async getMyBusinessCenterContracts( + businessCenterDomain: string, + contractType: string, + account: string, + ): Promise { const address = await this.nameResolver.getAddress(businessCenterDomain); - const contract = this.nameResolver.contractLoader.loadContract('BusinessCenterInterface', address); - const bcIndex = await this.nameResolver.executor.executeContractCall(contract, 'getMyIndex', {from: account}); - const indexContract = this.nameResolver.contractLoader.loadContract('DataStoreIndexInterface', bcIndex); - const contracts = await this.nameResolver.getArrayFromIndexContract(indexContract, this.nameResolver.soliditySha3(contractType)); + const contract = this.nameResolver.contractLoader.loadContract( + 'BusinessCenterInterface', address, + ); + const bcIndex = await this.nameResolver.executor.executeContractCall( + contract, 'getMyIndex', { from: account }, + ); + const indexContract = this.nameResolver.contractLoader.loadContract( + 'DataStoreIndexInterface', bcIndex, + ); + const contracts = await this.nameResolver.getArrayFromIndexContract( + indexContract, this.nameResolver.soliditySha3(contractType), + ); return contracts; } } diff --git a/src/profile/profile.spec.ts b/src/profile/profile.spec.ts index 0029f2be..f4e85cc1 100644 --- a/src/profile/profile.spec.ts +++ b/src/profile/profile.spec.ts @@ -20,41 +20,29 @@ import 'mocha'; import { expect, use } from 'chai'; import { isEqual } from 'lodash'; -import chaiAsPromised = require('chai-as-promised'); +import * as chaiAsPromised from 'chai-as-promised'; import { - ContractLoader, - KeyProvider, NameResolver, - SignerInternal, } from '@evan.network/dbcp'; -import { accountMap } from '../test/accounts'; -import { accounts } from '../test/accounts'; +import { accountMap, accounts } from '../test/accounts'; + import { configTestcore as config } from '../config-testcore'; import { createDefaultRuntime } from '../runtime'; -import { DataContract } from '../contracts/data-contract/data-contract'; -import { KeyExchange } from '../keyExchange'; -import { Mailbox } from '../mailbox'; import { Onboarding } from '../onboarding'; -import { RightsAndRoles } from '../contracts/rights-and-roles'; import { TestUtils } from '../test/test-utils'; use(chaiAsPromised); -describe('Profile helper', function() { +describe('Profile helper', function test() { this.timeout(600000); let ipfs; let ipld; let nameResolver: NameResolver; let ensName; let web3; - let dataContract: DataContract; - let keyExchange; - let mailbox; let executor; - let rightsAndRoles: RightsAndRoles; - let cryptoProvider; const sampleDesc = { title: 'sampleTest', description: 'desc', @@ -72,35 +60,15 @@ describe('Profile helper', function() { web3 = TestUtils.getWeb3(); ipfs = await TestUtils.getIpfs(); ipld = await TestUtils.getIpld(ipfs); - dataContract = await TestUtils.getDataContract(web3, ipld.ipfs) nameResolver = await TestUtils.getNameResolver(web3); ensName = nameResolver.getDomainName(config.nameResolver.domains.profile); - cryptoProvider = await TestUtils.getCryptoProvider(); - mailbox = new Mailbox({ - mailboxOwner: accounts[0], - nameResolver: await TestUtils.getNameResolver(web3), - ipfs: ipld.ipfs, - contractLoader: await TestUtils.getContractLoader(web3), - cryptoProvider, - keyProvider: TestUtils.getKeyProvider(), - defaultCryptoAlgo: 'aes', - }); - const keyExchangeOptions = { - mailbox: mailbox, - cryptoProvider, - defaultCryptoAlgo: 'aes', - account: accounts[0], - keyProvider: TestUtils.getKeyProvider(), - }; - keyExchange = new KeyExchange(keyExchangeOptions); const eventHub = await TestUtils.getEventHub(web3); executor = await TestUtils.getExecutor(web3); executor.eventHub = eventHub; - rightsAndRoles = await TestUtils.getRightsAndRoles(web3); }); it('should be able to be add contact keys', async () => { - let profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); + const profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); await profile.addContactKey(accounts[0], 'context a', 'key 0x01_a'); await profile.addContactKey(accounts[1], 'context a', 'key 0x02_a'); await profile.addContactKey(accounts[1], 'context b', 'key 0x02_b'); @@ -112,7 +80,7 @@ describe('Profile helper', function() { }); it('should be able to be add dapp bookmarks', async () => { - let profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); + const profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); await profile.addDappBookmark('sample1.test', sampleDesc); expect(await profile.getDappBookmark('sample1.test')).to.be.ok; @@ -130,7 +98,7 @@ describe('Profile helper', function() { const templates = { templates: 'can', have: { - any: 'format' + any: 'format', }, depending: ['on', 'your', 'needs'], }; @@ -145,7 +113,7 @@ describe('Profile helper', function() { }); it('should be able to save an encrypted profile to IPLD', async () => { - let profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); + const profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); await profile.addContactKey(accounts[0], 'context a', 'key 0x01_a'); await profile.addContactKey(accounts[1], 'context a', 'key 0x02_a'); await profile.addContactKey(accounts[1], 'context b', 'key 0x02_b'); @@ -178,13 +146,12 @@ describe('Profile helper', function() { const address = await nameResolver.getAddress(ensName); const contract = nameResolver.contractLoader.loadContract('ProfileIndexInterface', address); const valueToSet = '0x0000000000000000000000000000000000000004'; - let hash; const from = Object.keys(accountMap)[0]; - hash = await nameResolver.executor.executeContractCall(contract, 'getProfile', from, { from, }); + const hash = await nameResolver.executor.executeContractCall(contract, 'getProfile', from, { from }); await nameResolver.executor.executeContractTransaction( contract, 'setMyProfile', - { from, autoGas: 1.1, }, + { from, autoGas: 1.1 }, valueToSet, ); const newHash = await nameResolver.executor.executeContractCall(contract, 'getProfile', from, { from }); @@ -205,9 +172,9 @@ describe('Profile helper', function() { accountDetails: { profileType: 'company', accountName: 'test account', - } - }) - let profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); + }, + }); + const profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); await profile.loadForAccount(); await profile.addContactKey(accounts[0], 'context a', 'key 0x01_a'); await profile.addContactKey(accounts[1], 'context a', 'key 0x02_a'); @@ -239,7 +206,7 @@ describe('Profile helper', function() { }); it('should remove a bookmark from a given profile', async () => { - let profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); + const profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); await profile.addDappBookmark('sample1.test', sampleDesc); await profile.addDappBookmark('sample2.test', sampleDesc); @@ -258,7 +225,7 @@ describe('Profile helper', function() { accountDetails: { profileType: 'company', accountName: 'test account', - } + }, }); // simulate a different account with a different keyStore @@ -279,14 +246,13 @@ describe('Profile helper', function() { }); it.skip('should be able to set a contact as known', async () => { - const initRuntime = await TestUtils.getRuntime(accounts[0]); - let profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); + const profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); await Onboarding.createProfile(initRuntime, { accountDetails: { profileType: 'company', accountName: 'test account', - } + }, }); await profile.loadForAccount(); @@ -297,12 +263,12 @@ describe('Profile helper', function() { it.skip('should be able to set a contact as unknown', async () => { const initRuntime = await TestUtils.getRuntime(accounts[0]); - let profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); + const profile = await TestUtils.getProfile(web3, ipfs, ipld, accounts[0]); await Onboarding.createProfile(initRuntime, { accountDetails: { profileType: 'company', accountName: 'test account', - } + }, }); await profile.loadForAccount(); @@ -322,29 +288,29 @@ describe('Profile helper', function() { const dateString = Date.now().toString(); const companyProfileProperties = { registration: { - court: `trst ${ dateString }`, + court: `trst ${dateString}`, register: 'hra', - registerNumber: `qwer ${ dateString }`, - salesTaxID: `qw ${ dateString }`, + registerNumber: `qwer ${dateString}`, + salesTaxID: `qw ${dateString}`, }, contact: { country: 'DE', - city: `City ${ dateString }`, + city: `City ${dateString}`, postalCode: '12345', - streetAndNumber: `Street ${ dateString }`, - website: 'https://evan.network' - } + streetAndNumber: `Street ${dateString}`, + website: 'https://evan.network', + }, }; const deviceProfileProperties = { deviceDetails: { - dataStreamSettings: `dataStreamSettings ${ dateString }`, - location: `location ${ dateString }`, - manufacturer: `manufacturer ${ dateString }`, + dataStreamSettings: `dataStreamSettings ${dateString}`, + location: `location ${dateString}`, + manufacturer: `manufacturer ${dateString}`, owner: '0xcA4f9fF9e32a768BC68399B9F46d8A884089997d', - serialNumber: `serialNumber ${ dateString }`, + serialNumber: `serialNumber ${dateString}`, settings: { files: [] }, - type: { files: [] } - } + type: { files: [] }, + }, }; /** @@ -357,7 +323,7 @@ describe('Profile helper', function() { return createDefaultRuntime( await TestUtils.getWeb3(), await TestUtils.getIpfs(), - { mnemonic, password, } + { mnemonic, password }, ); } @@ -369,7 +335,7 @@ describe('Profile helper', function() { accountDetails: { profileType: 'user', accountName: 'test account', - } + }, }); const runtime = await getProfileRuntime(newMnemonic); @@ -377,7 +343,7 @@ describe('Profile helper', function() { accountDetails: { accountName: 'New company', profileType: 'company', - } + }, }); const accountDetails = await runtime.profile.getProfileProperty('accountDetails'); await expect(accountDetails.accountName).to.be.eq('New company'); @@ -391,7 +357,7 @@ describe('Profile helper', function() { accountDetails: { profileType: 'user', accountName: 'test account', - } + }, }); const runtime = await getProfileRuntime(newMnemonic); @@ -399,7 +365,7 @@ describe('Profile helper', function() { accountDetails: { accountName: 'New company', profileType: 'company', - } + }, }); const accountDetails = await runtime.profile.getProfileProperty('accountDetails'); await expect(accountDetails.accountName).to.be.eq('New company'); @@ -408,7 +374,7 @@ describe('Profile helper', function() { accountDetails: { accountName: 'Now it\'s a device', profileType: 'device', - } + }, }); await expect(promise).to.be.rejected; @@ -422,7 +388,7 @@ describe('Profile helper', function() { accountDetails: { profileType: 'user', accountName: 'test account', - } + }, }); const runtime = await getProfileRuntime(newMnemonic); @@ -430,7 +396,7 @@ describe('Profile helper', function() { accountDetails: { accountName: 'New device', profileType: 'device', - } + }, }); const accountDetails = await runtime.profile.getProfileProperty('accountDetails'); await expect(accountDetails.accountName).to.be.eq('New device'); @@ -444,7 +410,7 @@ describe('Profile helper', function() { accountDetails: { profileType: 'user', accountName: 'test account', - } + }, }); const runtime = await getProfileRuntime(newMnemonic); @@ -452,7 +418,7 @@ describe('Profile helper', function() { accountDetails: { accountName: 'custom profile', profileType: 'my own type', - } + }, }); await expect(promise).to.be.rejected; @@ -461,9 +427,9 @@ describe('Profile helper', function() { it('can save company profile specific properties to a profile of type company', async () => { const runtime = await getProfileRuntime(mnemonics.company); await runtime.profile.setProfileProperties(companyProfileProperties); - const [accountDetails, contact, registration] = - await Promise.all(['accountDetails', 'contact', 'registration'].map( - p => runtime.profile.getProfileProperty(p))); + const [accountDetails, contact, registration] = await Promise.all(['accountDetails', 'contact', 'registration'].map( + (p) => runtime.profile.getProfileProperty(p), + )); await expect(accountDetails.profileType).to.be.eq('company'); await expect(isEqual(companyProfileProperties.registration, registration)).to.be.true; await expect(isEqual(companyProfileProperties.contact, contact)).to.be.true; @@ -478,9 +444,9 @@ describe('Profile helper', function() { it('can save device profile specific properties to a profile of type device', async () => { const runtime = await getProfileRuntime(mnemonics.device); await runtime.profile.setProfileProperties(deviceProfileProperties); - const [accountDetails, deviceDetails] = - await Promise.all(['accountDetails', 'deviceDetails'].map( - p => runtime.profile.getProfileProperty(p))); + const [accountDetails, deviceDetails] = await Promise.all(['accountDetails', 'deviceDetails'].map( + (p) => runtime.profile.getProfileProperty(p), + )); await expect(accountDetails.profileType).to.be.eq('device'); await expect(isEqual(deviceProfileProperties.deviceDetails, deviceDetails)).to.be.true; }); diff --git a/src/profile/profile.ts b/src/profile/profile.ts index 865d0b4e..2ab12c81 100644 --- a/src/profile/profile.ts +++ b/src/profile/profile.ts @@ -75,17 +75,29 @@ export interface DappBookmark { */ export class Profile extends Logger { public activeAccount: string; + public contractLoader: ContractLoader; + public dataContract: DataContract; + public defaultCryptoAlgo: string; + public executor: Executor; + public ipld: Ipld; + public nameResolver: NameResolver; + public options: ProfileOptions; + public profileContainer: Container; + public profileContract: any; + public profileOwner: string; + public trees: any; + public treeLabels = { activeVerifications: 'activeVerifications', addressBook: 'addressBook', @@ -120,7 +132,7 @@ export class Profile extends Logger { ...Object.keys(accountTypes[type].template.properties), ]; // look for properties, that are not allowed in allowed fields (aka forbidden) - const notAllowed = Object.keys(data).filter(key => !allowedFields.includes(key)); + const notAllowed = Object.keys(data).filter((key) => !allowedFields.includes(key)); if (notAllowed.length) { throw new Error(`one or more fields are not allowed in profile: ${notAllowed}`); } @@ -153,11 +165,11 @@ export class Profile extends Logger { public async addBcContract(bc: string, address: string, data: any): Promise { this.throwIfNotOwner('add a contract to a specific scope'); this.ensureTree('contracts'); - const bcSet = await this.ipld.getLinkedGraph(this.trees['contracts'], bc); + const bcSet = await this.ipld.getLinkedGraph(this.trees.contracts, bc); if (!bcSet) { - await this.ipld.set(this.trees['contracts'], bc, {}, false); + await this.ipld.set(this.trees.contracts, bc, {}, false); } - await this.ipld.set(this.trees['contracts'], `${bc}/${address}`, data, false); + await this.ipld.set(this.trees.contracts, `${bc}/${address}`, data, false); } /** @@ -171,30 +183,34 @@ export class Profile extends Logger { public async addContactKey(address: string, context: string, key: string): Promise { this.log( `add contact key: account "${address}", context "${context}", key "${obfuscate(key)}"`, - 'debug'); + 'debug', + ); this.throwIfNotOwner('add a key for a contact to address book'); this.ensureTree('addressBook'); let addressHash; // check if address is already hashed if (address.length === 42) { - addressHash = this.nameResolver.soliditySha3.apply(this.nameResolver, [ - this.nameResolver.soliditySha3(address), - this.nameResolver.soliditySha3(this.activeAccount), - ].sort()); + addressHash = this.nameResolver.soliditySha3( + ...[ + this.nameResolver.soliditySha3(address), + this.nameResolver.soliditySha3(this.activeAccount), + ].sort(), + ); } else { addressHash = address; } - const keysSet = await this.ipld.getLinkedGraph(this.trees['addressBook'], `keys`); + const keysSet = await this.ipld.getLinkedGraph(this.trees.addressBook, 'keys'); if (!keysSet) { - await this.ipld.set(this.trees['addressBook'], 'keys', {}, true); + await this.ipld.set(this.trees.addressBook, 'keys', {}, true); } const contactSet = await this.ipld.getLinkedGraph( - this.trees['addressBook'], `keys/${addressHash}`); + this.trees.addressBook, `keys/${addressHash}`, + ); if (!contactSet) { - await this.ipld.set(this.trees['addressBook'], `keys/${addressHash}`, {}, true); + await this.ipld.set(this.trees.addressBook, `keys/${addressHash}`, {}, true); } - await this.ipld.set(this.trees['addressBook'], `keys/${addressHash}/${context}`, key, true); + await this.ipld.set(this.trees.addressBook, `keys/${addressHash}/${context}`, key, true); } /** @@ -206,7 +222,7 @@ export class Profile extends Logger { public async addContract(address: string, data: any): Promise { this.throwIfNotOwner('add a contract'); this.ensureTree('contracts'); - await this.ipld.set(this.trees['contracts'], address, data, false); + await this.ipld.set(this.trees.contracts, address, data, false); } /** @@ -222,11 +238,12 @@ export class Profile extends Logger { if (!address || !description) { throw new Error('no valid description or address given!'); } - await this.ipld.set(this.trees['bookmarkedDapps'], `bookmarkedDapps/${address}`, {}, true); + await this.ipld.set(this.trees.bookmarkedDapps, `bookmarkedDapps/${address}`, {}, true); const descriptionKeys = Object.keys(description); - for (let key of descriptionKeys) { + for (const key of descriptionKeys) { await this.ipld.set( - this.trees['bookmarkedDapps'], `bookmarkedDapps/${address}/${key}`, description[key], true); + this.trees.bookmarkedDapps, `bookmarkedDapps/${address}/${key}`, description[key], true, + ); } } @@ -241,16 +258,17 @@ export class Profile extends Logger { public async addProfileKey(address: string, key: string, value: string): Promise { this.throwIfNotOwner('add a profile value to an account'); this.ensureTree('addressBook'); - const profileSet = await this.ipld.getLinkedGraph(this.trees['addressBook'], `profile`); + const profileSet = await this.ipld.getLinkedGraph(this.trees.addressBook, 'profile'); if (!profileSet) { - await this.ipld.set(this.trees['addressBook'], `profile`, {}, true); + await this.ipld.set(this.trees.addressBook, 'profile', {}, true); } const addressSet = await this.ipld.getLinkedGraph( - this.trees['addressBook'], `profile/${address}`); + this.trees.addressBook, `profile/${address}`, + ); if (!addressSet) { - await this.ipld.set(this.trees['addressBook'], `profile/${address}`, {}, true); + await this.ipld.set(this.trees.addressBook, `profile/${address}`, {}, true); } - await this.ipld.set(this.trees['addressBook'], `profile/${address}/${key}`, value, true); + await this.ipld.set(this.trees.addressBook, `profile/${address}/${key}`, value, true); } /** @@ -262,7 +280,7 @@ export class Profile extends Logger { public async addPublicKey(key: string): Promise { this.throwIfNotOwner('set public key'); this.ensureTree('publicKey'); - await this.ipld.set(this.trees['publicKey'], 'publicKey', key, true); + await this.ipld.set(this.trees.publicKey, 'publicKey', key, true); } /** @@ -275,9 +293,11 @@ export class Profile extends Logger { const ensName = this.nameResolver.getDomainName(this.nameResolver.config.domains.profile); const address = await this.nameResolver.getAddress(ensName); const indexContract = this.nameResolver.contractLoader.loadContract( - 'ProfileIndexInterface', address); + 'ProfileIndexInterface', address, + ); const profileContractAddress = await this.executor.executeContractCall( - indexContract, 'getProfile', this.profileOwner, { from: this.activeAccount, }); + indexContract, 'getProfile', this.profileOwner, { from: this.activeAccount }, + ); return profileContractAddress !== '0x0000000000000000000000000000000000000000'; } catch (ex) { this.log(`error occurred while checking if profile exists; ${ex.message || ex}`, 'debug'); @@ -346,7 +366,7 @@ export class Profile extends Logger { if (!this.trees[this.treeLabels.bookmarkedDapps]) { await this.loadForAccount(this.treeLabels.bookmarkedDapps); } - return this.ipld.getLinkedGraph(this.trees[this.treeLabels.bookmarkedDapps], `bookmarkedDapps`); + return this.ipld.getLinkedGraph(this.trees[this.treeLabels.bookmarkedDapps], 'bookmarkedDapps'); } /** @@ -361,10 +381,12 @@ export class Profile extends Logger { let addressHash; // check if address is already hashed if (address.length === 42) { - addressHash = this.nameResolver.soliditySha3.apply(this.nameResolver, [ - this.nameResolver.soliditySha3(address), - this.nameResolver.soliditySha3(this.activeAccount), - ].sort()); + addressHash = this.nameResolver.soliditySha3( + ...[ + this.nameResolver.soliditySha3(address), + this.nameResolver.soliditySha3(this.activeAccount), + ].sort(), + ); } else { addressHash = address; } @@ -372,7 +394,8 @@ export class Profile extends Logger { await this.loadForAccount(this.treeLabels.addressBook); } return this.ipld.getLinkedGraph( - this.trees[this.treeLabels.addressBook], `keys/${addressHash}/${context}`); + this.trees[this.treeLabels.addressBook], `keys/${addressHash}/${context}`, + ); } /** @@ -400,7 +423,8 @@ export class Profile extends Logger { await this.loadForAccount(this.treeLabels.contracts); } return this.ipld.getLinkedGraph( - this.trees[this.treeLabels.contracts], this.treeLabels.contracts); + this.trees[this.treeLabels.contracts], this.treeLabels.contracts, + ); } /** @@ -415,7 +439,8 @@ export class Profile extends Logger { await this.loadForAccount(this.treeLabels.bookmarkedDapps); } return this.ipld.getLinkedGraph( - this.trees[this.treeLabels.bookmarkedDapps], `bookmarkedDapps/${address}`); + this.trees[this.treeLabels.bookmarkedDapps], `bookmarkedDapps/${address}`, + ); } /** @@ -433,7 +458,7 @@ export class Profile extends Logger { false, false, ); - return value.substr(-1) === '0' ? false : true; + return value.substr(-1) !== '0'; } /** @@ -475,9 +500,9 @@ export class Profile extends Logger { throw new Error(`property "${property}" is type "array", which is not supported`); } const value = await this.profileContainer.getEntry(property); - return value !== '0x0000000000000000000000000000000000000000000000000000000000000000' ? - value : - null; + return value !== '0x0000000000000000000000000000000000000000000000000000000000000000' + ? value + : null; } /** @@ -493,7 +518,8 @@ export class Profile extends Logger { await this.loadForAccount(this.treeLabels.addressBook); } return this.ipld.getLinkedGraph( - this.trees[this.treeLabels.addressBook], `profile/${address}/${key}`); + this.trees[this.treeLabels.addressBook], `profile/${address}/${key}`, + ); } /** @@ -530,23 +556,23 @@ export class Profile extends Logger { * @param {string} tree tree to load ('bookmarkedDapps', 'contracts', ...) * @return {Promise} resolved when done */ + // eslint-disable-next-line consistent-return public async loadForAccount(tree?: string): Promise { // ensure profile contract if (!this.profileContract) { const ensName = this.nameResolver.getDomainName(this.nameResolver.config.domains.profile); const address = await this.nameResolver.getAddress(ensName); - const indexContract = - this.nameResolver.contractLoader.loadContract('ProfileIndexInterface', address); + const indexContract = this.nameResolver.contractLoader.loadContract('ProfileIndexInterface', address); const profileContractAddress = await this.executor.executeContractCall( - indexContract, 'getProfile', this.profileOwner, { from: this.activeAccount, }); + indexContract, 'getProfile', this.profileOwner, { from: this.activeAccount }, + ); if (profileContractAddress === '0x0000000000000000000000000000000000000000') { throw new Error(`no profile found for account "${this.profileOwner}"`); } else { - const contractAddress = profileContractAddress.length === 66 ? - this.executor.web3.utils.toChecksumAddress(profileContractAddress.substr(0, 42)) : - profileContractAddress; - this.profileContract = - this.contractLoader.loadContract('DataContractInterface', contractAddress); + const contractAddress = profileContractAddress.length === 66 + ? this.executor.web3.utils.toChecksumAddress(profileContractAddress.substr(0, 42)) + : profileContractAddress; + this.profileContract = this.contractLoader.loadContract('DataContractInterface', contractAddress); this.profileContainer = new Container( { ...this.options, verifications: null, web3: this.options.executor.web3 }, { accountId: this.activeAccount, address: this.profileContract.address }, @@ -559,10 +585,12 @@ export class Profile extends Logger { let hash; if (tree === this.treeLabels.publicKey) { hash = await this.dataContract.getEntry( - this.profileContract, tree, this.activeAccount, false, false); + this.profileContract, tree, this.activeAccount, false, false, + ); } else { hash = await this.dataContract.getEntry( - this.profileContract, tree, this.activeAccount, false, true); + this.profileContract, tree, this.activeAccount, false, true, + ); } if (hash === '0x0000000000000000000000000000000000000000000000000000000000000000') { this.trees[tree] = { @@ -571,9 +599,8 @@ export class Profile extends Logger { contracts: {}, }; return Promise.resolve(); - } else { - await this.loadFromIpld(tree, hash); } + await this.loadFromIpld(tree, hash); } } @@ -592,7 +619,7 @@ export class Profile extends Logger { try { loaded = await this.ipld.getLinkedGraph(ipldIpfsHash); } catch (e) { - this.log(`could not load profile from ipld ${ e.message || e }`, 'error'); + this.log(`could not load profile from ipld ${e.message || e}`, 'error'); loaded = { bookmarkedDapps: {}, addressBook: {}, @@ -613,10 +640,10 @@ export class Profile extends Logger { public async removeBcContract(bc: string, address: string): Promise { this.throwIfNotOwner('remove a contract from a specific scope'); this.ensureTree('contracts'); - const bcSet = await this.ipld.getLinkedGraph(this.trees['contracts'], bc); + const bcSet = await this.ipld.getLinkedGraph(this.trees.contracts, bc); if (bcSet) { - await this.ipld.remove(this.trees['contracts'], `${bc}/${address}`); + await this.ipld.remove(this.trees.contracts, `${bc}/${address}`); } } @@ -628,10 +655,12 @@ export class Profile extends Logger { */ public async removeContact(address: string): Promise { this.throwIfNotOwner('remove a contract'); - const addressHash = this.nameResolver.soliditySha3.apply(this.nameResolver, [ - this.nameResolver.soliditySha3(address), - this.nameResolver.soliditySha3(this.activeAccount), - ].sort()); + const addressHash = this.nameResolver.soliditySha3( + ...[ + this.nameResolver.soliditySha3(address), + this.nameResolver.soliditySha3(this.activeAccount), + ].sort(), + ); const addressBook = await this.getAddressBook(); delete addressBook.keys[addressHash]; delete addressBook.profile[address]; @@ -645,11 +674,11 @@ export class Profile extends Logger { */ public async removeDappBookmark(address: string): Promise { this.throwIfNotOwner('remove a dapp bookmark'); - if (!address ) { + if (!address) { throw new Error('no valid address given!'); } this.ensureTree('bookmarkedDapps'); - await this.ipld.remove(this.trees['bookmarkedDapps'], `bookmarkedDapps/${address}`); + await this.ipld.remove(this.trees.bookmarkedDapps, `bookmarkedDapps/${address}`); } /** @@ -668,7 +697,7 @@ export class Profile extends Logger { this.trees[this.treeLabels.bookmarkedDapps], this.treeLabels.bookmarkedDapps, bookmarks, - true + true, ); } @@ -710,7 +739,7 @@ export class Profile extends Logger { this.trees[this.treeLabels.activeVerifications], this.treeLabels.activeVerifications, verifications, - true + true, ); } @@ -727,7 +756,7 @@ export class Profile extends Logger { this.profileContract, 'contacts', accountId, - `0x${(contactKnown ? '1' : '0').padStart(64, '0')}`, // cast bool to bytes32 + `0x${(contactKnown ? '1' : '0').padStart(64, '0')}`, // cast bool to bytes32 this.activeAccount, false, false, @@ -784,29 +813,29 @@ export class Profile extends Logger { try { accountDetails = await this.getProfileProperty('accountDetails'); } catch (ex) { - this.log('could not get account details, will use this profile as user; ' + - '${ex.message || ex}', 'warning'); + this.log(`could not get account details, will use this profile as user; ${ + ex.message}` || ex, 'warning'); } // get profile type and forbid invalid type transitions - let profileType = (accountDetails && accountDetails.profileType) ? - accountDetails.profileType : 'user'; - if (data.accountDetails && - data.accountDetails.profileType && - data.accountDetails.profileType !== profileType && - profileType !== 'user') { - throw new Error(`invalid profile type change ${accountDetails.profileType} ` + - `--> ${data.accountDetails.profileType}, change not allowed`); - } - if (data.accountDetails && - data.accountDetails.profileType && - !Object.keys(accountTypes).includes(data.accountDetails.profileType)) { - throw new Error(`invalid profile type change ${accountDetails.profileType} ` + - `--> ${data.accountDetails.profileType}, target type not supported`); - } - if (data.accountDetails && - data.accountDetails.profileType && - data.accountDetails.profileType !== profileType) { + let profileType = (accountDetails && accountDetails.profileType) + ? accountDetails.profileType : 'user'; + if (data.accountDetails + && data.accountDetails.profileType + && data.accountDetails.profileType !== profileType + && profileType !== 'user') { + throw new Error(`invalid profile type change ${accountDetails.profileType} ` + + `--> ${data.accountDetails.profileType}, change not allowed`); + } + if (data.accountDetails + && data.accountDetails.profileType + && !Object.keys(accountTypes).includes(data.accountDetails.profileType)) { + throw new Error(`invalid profile type change ${accountDetails.profileType} ` + + `--> ${data.accountDetails.profileType}, target type not supported`); + } + if (data.accountDetails + && data.accountDetails.profileType + && data.accountDetails.profileType !== profileType) { profileType = data.accountDetails.profileType; } @@ -827,25 +856,29 @@ export class Profile extends Logger { this.throwIfNotOwner('store secured profile data'); await this.ensurePropertyInProfile(tree); if (ipldHash) { - this.log(`store tree "${tree}" with given hash to profile contract for account ` + - `"${this.activeAccount}"`); + this.log(`store tree "${tree}" with given hash to profile contract for account ` + + `"${this.activeAccount}"`); if (tree === this.treeLabels.publicKey) { await this.dataContract.setEntry( - this.profileContract, tree, ipldHash, this.activeAccount, false, false); + this.profileContract, tree, ipldHash, this.activeAccount, false, false, + ); } else { await this.dataContract.setEntry( - this.profileContract, tree, ipldHash, this.activeAccount, false, false); + this.profileContract, tree, ipldHash, this.activeAccount, false, false, + ); } } else { - this.log(`store tree "${tree}" to ipld and then to profile contract for account ` + - `"${this.activeAccount}"`); + this.log(`store tree "${tree}" to ipld and then to profile contract for account ` + + `"${this.activeAccount}"`); const stored = await this.storeToIpld(tree); if (tree === this.treeLabels.publicKey) { await this.dataContract.setEntry( - this.profileContract, tree, stored, this.activeAccount, false, false); + this.profileContract, tree, stored, this.activeAccount, false, false, + ); } else { await this.dataContract.setEntry( - this.profileContract, tree, stored, this.activeAccount, false, true); + this.profileContract, tree, stored, this.activeAccount, false, true, + ); } await this.loadForAccount(tree); } @@ -858,7 +891,7 @@ export class Profile extends Logger { * @return {Promise} hash of the ipfs file */ public async storeToIpld(tree: string): Promise { - return await this.ipld.store(this.trees[tree]); + return this.ipld.store(this.trees[tree]); } /** @@ -868,8 +901,8 @@ export class Profile extends Logger { */ private throwIfNotOwner(action: string) { if (this.activeAccount !== this.profileOwner) { - throw new Error(`tried to ${action} on "${this.profileOwner}"s profile with ` + - `"${this.activeAccount}", this is only supported for the owner of a profile`); + throw new Error(`tried to ${action} on "${this.profileOwner}"s profile with ` + + `"${this.activeAccount}", this is only supported for the owner of a profile`); } } @@ -881,9 +914,11 @@ export class Profile extends Logger { */ private async ensurePropertyInProfile(tree: string): Promise { const hash = this.options.rightsAndRoles.getOperationCapabilityHash( - tree, PropertyType.Entry, ModificationType.Set); + tree, PropertyType.Entry, ModificationType.Set, + ); if (!await this.options.rightsAndRoles.canCallOperation( - this.profileContract.options.address, this.activeAccount, hash)) { + this.profileContract.options.address, this.activeAccount, hash, + )) { await this.options.rightsAndRoles.setOperationPermission( this.profileContract, this.activeAccount, @@ -905,11 +940,11 @@ export class Profile extends Logger { if (!this.trees[tree] && tree === 'publicKey') { this.trees[tree] = { cryptoInfo: { - algorithm: 'unencrypted' + algorithm: 'unencrypted', }, }; } else if (!this.trees[tree]) { this.trees[tree] = {}; } } -} \ No newline at end of file +} diff --git a/src/profile/types/types.ts b/src/profile/types/types.ts index f2eae2a1..8a6c984d 100644 --- a/src/profile/types/types.ts +++ b/src/profile/types/types.ts @@ -24,5 +24,5 @@ import user from './user'; export { company, device, - user -} + user, +}; diff --git a/src/runtime.spec.ts b/src/runtime.spec.ts index 94436c5c..86ec5424 100644 --- a/src/runtime.spec.ts +++ b/src/runtime.spec.ts @@ -18,15 +18,16 @@ */ import 'mocha'; -import { expect, use, } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; import { createDefaultRuntime } from './runtime'; import { TestUtils } from './test/test-utils'; import { accountMap, accounts } from './test/accounts'; +use(chaiAsPromised); -describe('Runtime', function() { +describe('Runtime', function test() { this.timeout(600000); let web3; @@ -40,8 +41,8 @@ describe('Runtime', function() { runtimeConfig = { accountMap, keyConfig: { - [web3.utils.soliditySha3(accounts[1])]: '0030c5e7394585400b1f00d1267b27c3a80080f9000000000000000000000012' - } + [web3.utils.soliditySha3(accounts[1])]: '0030c5e7394585400b1f00d1267b27c3a80080f9000000000000000000000012', + }, }; }); @@ -51,9 +52,8 @@ describe('Runtime', function() { }); it('should create a new runtime and parse accountid and password in keyConfig', async () => { - const tmpRuntimeConfig = runtimeConfig; - tmpRuntimeConfig.keyConfig[accounts[0]] = 'Test1234'; + tmpRuntimeConfig.keyConfig[accounts[0]] = 'Test1234'; const runtime = await createDefaultRuntime(web3, dfs, runtimeConfig); expect(runtime).to.be.ok; expect(Object.keys(runtime.keyProvider.keys).length).to.eq(3); @@ -62,7 +62,7 @@ describe('Runtime', function() { it('should create a new and valid runtime with a mnemonic and a password', async () => { const runtime = await createDefaultRuntime(web3, dfs, { mnemonic: 'annual lyrics orbit slight object space jeans ethics broccoli umbrella entry couch', - password: 'Test1234' + password: 'Test1234', }); expect(runtime).to.be.ok; expect(Object.keys(runtime.keyProvider.keys).length).to.eq(2); @@ -70,11 +70,26 @@ describe('Runtime', function() { it('should create a new and valid runtime with a mnemonic and a password and merge with given accounts', async () => { const tmpRuntimeConfig = runtimeConfig; - tmpRuntimeConfig.keyConfig[accounts[0]] = 'Test1234'; + tmpRuntimeConfig.keyConfig[accounts[0]] = 'Test1234'; tmpRuntimeConfig.mnemonic = 'annual lyrics orbit slight object space jeans ethics broccoli umbrella entry couch'; tmpRuntimeConfig.password = 'Test1234'; const runtime = await createDefaultRuntime(web3, dfs, tmpRuntimeConfig); expect(runtime).to.be.ok; expect(Object.keys(runtime.keyProvider.keys).length).to.eq(5); }); + + it('should NOT create a new and valid runtime with only passing mnemonic and empty account map', async () => { + const runtimePromise = createDefaultRuntime(web3, dfs, { + mnemonic: 'annual lyrics orbit slight object space jeans ethics broccoli umbrella entry couch', + accountMap: { }, + }); + await expect(runtimePromise).to.be.rejected; + }); + + it('should NOT create a new and valid runtime with account map is invalid', async () => { + const runtimePromise = createDefaultRuntime(web3, dfs, { + accountMap: { }, + }); + await expect(runtimePromise).to.be.rejectedWith('accountMap invalid'); + }); }); diff --git a/src/runtime.ts b/src/runtime.ts index 7cf93c8c..1994f701 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -17,79 +17,85 @@ the following URL: https://evan.network/license/ */ +import { IpfsLib } from './dfs/ipfs-lib'; +import { configCore } from './config-core'; +import { configTestcore } from './config-testcore'; +import { getEnvironment } from './common/utils'; import { AccountStore, + Aes, + AesBlob, + AesEcb, + BaseContract, ContractLoader, + CryptoProvider, + DataContract, + Description, DfsInterface, + Did, + EncryptionWrapper, EventHub, Executor, + Ipfs, + Ipld, + KeyExchange, KeyProvider, Logger, + Mailbox, + NameResolver, + Onboarding, + Payments, + Profile, + RightsAndRoles, + ServiceContract, + Sharing, + SignerIdentity, SignerInterface, SignerInternal, Unencrypted, -} from '@evan.network/dbcp'; - -import { Aes } from './encryption/aes'; -import { AesBlob } from './encryption/aes-blob'; -import { AesEcb } from './encryption/aes-ecb'; -import { BaseContract } from './contracts/base-contract/base-contract'; -import { configCore } from './config-core'; -import { configTestcore } from './config-testcore'; -import { CryptoProvider } from './encryption/crypto-provider'; -import { DataContract } from './contracts/data-contract/data-contract'; -import { Description } from './shared-description'; -import { EncryptionWrapper } from './encryption/encryption-wrapper'; -import { getEnvironment } from './common/utils'; -import { Ipfs } from './dfs/ipfs'; -import { IpfsLib } from './dfs/ipfs-lib'; -import { Ipld } from './dfs/ipld'; -import { KeyExchange } from './keyExchange'; -import { Mailbox } from './mailbox'; -import { NameResolver } from './name-resolver'; -import { Onboarding } from './onboarding'; -import { Payments } from './payments'; -import { Profile } from './profile/profile'; -import { RightsAndRoles } from './contracts/rights-and-roles'; -import { ServiceContract } from './contracts/service-contract/service-contract'; -import { Sharing } from './contracts/sharing'; -import { Verifications } from './verifications/verifications'; -import { Votings } from './votings/votings'; + Vc, + Verifications, + Votings, +} from './index'; /** * runtime for interacting with dbcp, including helpers for transactions & co */ export interface Runtime { - accountStore?: AccountStore, - activeAccount?: string, - baseContract?: BaseContract, - contractLoader?: ContractLoader, - contracts?: any, - cryptoProvider?: CryptoProvider, - dataContract?: DataContract, - description?: Description, - dfs?: DfsInterface, - encryptionWrapper?: EncryptionWrapper, - environment?: string, - eventHub?: EventHub, - executor?: Executor, - ipld?: Ipld, - keyExchange?: KeyExchange, - keyProvider?: KeyProvider, - logger?: Logger, - mailbox?: Mailbox, - nameResolver?: NameResolver, - onboarding?: Onboarding, - payments?: Payments, - profile?: Profile, - rightsAndRoles?: RightsAndRoles, - serviceContract?: ServiceContract, - sharing?: Sharing, - signer?: SignerInterface, - verifications?: Verifications, - votings?: Votings, - web3?: any, -}; + accountStore?: AccountStore; + activeAccount?: string; + activeIdentity?: string; + baseContract?: BaseContract; + contractLoader?: ContractLoader; + contracts?: any; + cryptoProvider?: CryptoProvider; + dataContract?: DataContract; + description?: Description; + dfs?: DfsInterface; + did?: Did; + encryptionWrapper?: EncryptionWrapper; + environment?: string; + eventHub?: EventHub; + executor?: Executor; + ipld?: Ipld; + keyExchange?: KeyExchange; + keyProvider?: KeyProvider; + logger?: Logger; + mailbox?: Mailbox; + nameResolver?: NameResolver; + onboarding?: Onboarding; + payments?: Payments; + profile?: Profile; + rightsAndRoles?: RightsAndRoles; + serviceContract?: ServiceContract; + sharing?: Sharing; + signer?: SignerInterface; + underlyingAccount?: string; + vc?: Vc; + verifications?: Verifications; + votings?: Votings; + web3?: any; +} /** * create new runtime instance @@ -99,7 +105,9 @@ export interface Runtime { * @param {any} runtimeConfig configuration values * @return {Promise} runtime instance */ -export async function createDefaultRuntime(web3: any, dfs: DfsInterface, runtimeConfig: any, options: Runtime = { }): Promise { +export async function createDefaultRuntime( + web3: any, dfs: DfsInterface, runtimeConfig: any, options: Runtime = { }, +): Promise { // determine chain this runtime is created for const environment = await getEnvironment(web3); const config = environment === 'core' ? configCore : configTestcore; @@ -111,26 +119,29 @@ export async function createDefaultRuntime(web3: any, dfs: DfsInterface, runtime // if this function is used within node and no browser context exists, load the // @evan.network/smart-contracts-core normally and use the Solc functionalities to parse and // retrieve contracts - let contracts = options.contracts; + let { contracts } = options; if (!contracts) { - if (typeof global === 'undefined' || !(global).localStorage) { + if (typeof global === 'undefined' || !(global as any).localStorage) { // get/compile smart contracts // It is possible to load contracts from non-default locations - const solcCfg = { compileContracts: false, } + const solcCfg = { compileContracts: false }; if (runtimeConfig.contractsLoadPath) { - solcCfg['destinationPath'] = runtimeConfig.contractsLoadPath; + (solcCfg as any).destinationPath = runtimeConfig.contractsLoadPath; } + // eslint-disable-next-line const smartContract = require('@evan.network/smart-contracts-core'); - const solc = new smartContract.Solc({ config: solcCfg, log, }); + const solc = new smartContract.Solc({ config: solcCfg, log }); await solc.ensureCompiled( - runtimeConfig.additionalContractsPaths || [], solcCfg['destinationPath']); + runtimeConfig.additionalContractsPaths || [], (solcCfg as any).destinationPath, + ); contracts = solc.getContracts(); } else { // if this lib is used within the browser using browserify, smart-contracts-core needs to be // defined externaly (normally defined by @evan.network/ui-dapp-browser) to return the abis // directly as json + // eslint-disable-next-line const originalContracts = require('@evan.network/smart-contracts-core'); contracts = { }; @@ -143,43 +154,69 @@ export async function createDefaultRuntime(web3: any, dfs: DfsInterface, runtime } // web3 contract interfaces - const contractLoader = options.contractLoader || - new ContractLoader({ contracts, log, web3, }); + const contractLoader = options.contractLoader + || new ContractLoader({ contracts, log, web3 }); // check if mnemonic and password are given if (runtimeConfig.mnemonic && runtimeConfig.password) { const tempConfig: any = await Onboarding.generateRuntimeConfig( runtimeConfig.mnemonic, runtimeConfig.password, - web3 + web3, ); if (!runtimeConfig.accountMap) { - runtimeConfig.accountMap = {}; + // eslint-disable-next-line no-param-reassign + (runtimeConfig as any).accountMap = {}; } if (!runtimeConfig.keyConfig) { - runtimeConfig.keyConfig = {}; + // eslint-disable-next-line no-param-reassign + (runtimeConfig as any).keyConfig = {}; } Object.assign(runtimeConfig.accountMap, tempConfig.accountMap); Object.assign(runtimeConfig.keyConfig, tempConfig.keyConfig); + } else if (!runtimeConfig.accountMap + || !(Object.keys(runtimeConfig.accountMap).length)) { + throw new Error('accountMap invalid'); } const activeAccount = Object.keys(runtimeConfig.accountMap)[0]; // executor - const accountStore = options.accountStore || - new AccountStore({ accounts: runtimeConfig.accountMap, log, }); - const signerConfig = {}; - if (runtimeConfig.hasOwnProperty('gasPrice')) { + const accountStore = options.accountStore + || new AccountStore({ accounts: runtimeConfig.accountMap, log }); + const signerConfig = {} as any; + if (Object.prototype.hasOwnProperty.call(runtimeConfig, 'gasPrice')) { signerConfig.gasPrice = runtimeConfig.gasPrice; } else { signerConfig.gasPrice = `${200e9}`; } - const signer = options.signer || - new SignerInternal({ accountStore, contractLoader, config: signerConfig, log, web3, }); + const signerInternal = options.signer + || new SignerInternal({ + accountStore, contractLoader, config: signerConfig, log, web3, + }); + let signer; + if (runtimeConfig.useIdentity) { + signer = new SignerIdentity( + { + contractLoader, + verifications: null, // filled later on + web3, + }, + ); + } else { + signer = signerInternal; + } + const executor = options.executor || new Executor( - Object.assign({ config, log, signer, web3, }, - runtimeConfig.options ? runtimeConfig.options.Executor : {})); + { + config, + log, + signer, + web3, + ...(runtimeConfig.options ? runtimeConfig.options.Executor : {}), + }, + ); await executor.init({}); const nameResolver = options.nameResolver || new NameResolver({ config: runtimeConfig.nameResolver || config.nameResolver, @@ -197,47 +234,56 @@ export async function createDefaultRuntime(web3: any, dfs: DfsInterface, runtime executor.eventHub = eventHub; // check if the dfs remoteNode matches our ipfslib - if (!(dfs as Ipfs).remoteNode as any instanceof IpfsLib) { + if (!((dfs as Ipfs).remoteNode as any instanceof IpfsLib)) { + // eslint-disable-next-line no-param-reassign (dfs as Ipfs).remoteNode = new IpfsLib(config.ipfsConfig); } - (dfs as Ipfs).setRuntime({signer, activeAccount, web3}); + (dfs as Ipfs).setRuntime({ signer, activeAccount, web3 }); // encryption const cryptoConfig = {}; - cryptoConfig['aes'] = new Aes({ log }); - cryptoConfig['unencrypted'] = new Unencrypted({ log }); - cryptoConfig['aesBlob'] = new AesBlob({ dfs, log }); - cryptoConfig['aesEcb'] = new AesEcb({ log }); + // eslint-disable-next-line no-param-reassign + (cryptoConfig as any).aes = new Aes({ log }); + // (cryptoConfig as any)-disable-next-line no-param-reassign + (cryptoConfig as any).unencrypted = new Unencrypted({ log }); + // eslint-disable-next-line no-param-reassign + (cryptoConfig as any).aesBlob = new AesBlob({ dfs, log }); + // eslint-disable-next-line no-param-reassign + (cryptoConfig as any).aesEcb = new AesEcb({ log }); const cryptoProvider = new CryptoProvider(cryptoConfig); // check and modify if any accountid with password is provided if (runtimeConfig.keyConfig) { - for (let accountId in runtimeConfig.keyConfig) { + for (const accountId in runtimeConfig.keyConfig) { // check if the key is a valid accountId if (accountId.length === 42) { const sha9Account = web3.utils.soliditySha3.apply( web3.utils.soliditySha3, [ web3.utils.soliditySha3(accountId), - web3.utils.soliditySha3(accountId) - ].sort() + web3.utils.soliditySha3(accountId), + ].sort(), ); - const sha3Account = web3.utils.soliditySha3(accountId) + const sha3Account = web3.utils.soliditySha3(accountId); const dataKey = web3.utils .keccak256(accountId + runtimeConfig.keyConfig[accountId]) .replace(/0x/g, ''); // now add the different hashed accountids and datakeys to the runtimeconfig + // eslint-disable-next-line no-param-reassign runtimeConfig.keyConfig[sha3Account] = dataKey; + // eslint-disable-next-line no-param-reassign runtimeConfig.keyConfig[sha9Account] = dataKey; // at least delete the old key + // eslint-disable-next-line no-param-reassign delete runtimeConfig.keyConfig[accountId]; } } } - const keyProvider = options.keyProvider || new KeyProvider({ keys: runtimeConfig.keyConfig, log, }); + const keyProvider = options.keyProvider + || new KeyProvider({ keys: runtimeConfig.keyConfig, log }); // description const description = options.description || new Description({ @@ -303,7 +349,7 @@ export async function createDefaultRuntime(web3: any, dfs: DfsInterface, runtime // 'own' key provider, that won't be linked to profile and used in 'own' ipld // this prevents key lookup infinite loops - const keyProviderOwn = new KeyProvider({ keys: runtimeConfig.keyConfig, log, }); + const keyProviderOwn = new KeyProvider({ keys: runtimeConfig.keyConfig, log }); const ipldOwn = new Ipld({ ipfs: dfs as Ipfs, keyProvider: keyProviderOwn, @@ -387,15 +433,59 @@ export async function createDefaultRuntime(web3: any, dfs: DfsInterface, runtime }); const verifications = options.verifications || new Verifications({ - accountStore: accountStore, - contractLoader: contractLoader, + accountStore, + contractLoader, config, description, - dfs: dfs, - executor: executor, + dfs, + executor, log, - nameResolver: nameResolver, - }) + nameResolver, + }); + + let activeIdentity: string; + let underlyingAccount: string; + if (runtimeConfig.useIdentity) { + activeIdentity = await verifications.getIdentityForAccount(activeAccount, true); + underlyingAccount = activeAccount; + signer.updateConfig( + { verifications }, + { + activeIdentity, + underlyingAccount, + underlyingSigner: signerInternal, + }, + ); + } + + let did: Did; + let vc: Vc; + if (runtimeConfig.useIdentity) { + did = new Did({ + contractLoader, + dfs, + executor, + nameResolver, + signerIdentity: signer, + web3, + }); + vc = new Vc( + { + activeAccount, + accountStore, + contractLoader, + dfs, + did, + executor, + nameResolver, + signerIdentity: signer, + verifications, + web3, + }, + { credentialStatusEndpoint: config.smartAgents.didAndVc.vcRevokationStatusEndpoint }, + ); + } + if (await profile.exists()) { logger.log(`profile for ${activeAccount} exists, fetching keys`, 'debug'); @@ -405,12 +495,15 @@ export async function createDefaultRuntime(web3: any, dfs: DfsInterface, runtime await profile.getContactKey(activeAccount, 'dataKey'), ); } catch (ex) { - logger.log(`fetching keys for ${activeAccount} failed with "${ex.msg || ex}", removing profile from runtime`, 'warning'); + logger.log( + `fetching keys for ${activeAccount} failed with "${ex.msg || ex}", ` + + 'removing profile from runtime', 'warning', + ); profile = null; keyProvider.profile = null; } } else { - logger.log(`profile for ${activeAccount} doesn't exist`, 'debug') + logger.log(`profile for ${activeAccount} doesn't exist`, 'debug'); } const onboarding = options.onboarding || new Onboarding({ @@ -432,7 +525,7 @@ export async function createDefaultRuntime(web3: any, dfs: DfsInterface, runtime contractLoader, executor, log, - web3 + web3, }); const encryptionWrapper = options.encryptionWrapper || new EncryptionWrapper({ @@ -474,5 +567,10 @@ export async function createDefaultRuntime(web3: any, dfs: DfsInterface, runtime verifications, votings, web3, + // optional properties + ...(activeIdentity && { activeIdentity }), + ...(did && { did }), + ...(vc && { vc }), + ...(underlyingAccount && { underlyingAccount }), }; -}; +} diff --git a/src/shared-description.spec.ts b/src/shared-description.spec.ts index fe39c81c..eb5d95be 100644 --- a/src/shared-description.spec.ts +++ b/src/shared-description.spec.ts @@ -18,22 +18,18 @@ */ import 'mocha'; -import { expect, use, } from 'chai'; -import chaiAsPromised = require('chai-as-promised'); +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; import { ContractLoader, - Envelope, Executor, - Ipfs, KeyProvider, NameResolver, - Unencrypted, } from '@evan.network/dbcp'; import { accounts } from './test/accounts'; import { Aes } from './encryption/aes'; -import { BaseContract } from './contracts/base-contract/base-contract'; import { configTestcore as config } from './config-testcore'; import { CryptoProvider } from './encryption/crypto-provider'; import { DataContract } from './contracts/data-contract/data-contract'; @@ -45,32 +41,30 @@ import { TestUtils } from './test/test-utils'; use(chaiAsPromised); const testAddressPrefix = 'testDapp'; -/* tslint:disable:quotemark */ const sampleDescription = { - "name": "test description", - "description": "description used in tests.", - "author": "description test user", - "version": "0.0.1", - "dbcpVersion": 1, - "dapp": { - "dependencies": { - "angular-bc": "^1.0.0", - "angular-core": "^1.0.0", - "angular-libs": "^1.0.0" - }, - "entrypoint": "task.js", - "files": [ - "task.js", - "task.css" + name: 'test description', + description: 'description used in tests.', + author: 'description test user', + version: '0.0.1', + dbcpVersion: 1, + dapp: { + dependencies: { + 'angular-bc': '^1.0.0', + 'angular-core': '^1.0.0', + 'angular-libs': '^1.0.0', + }, + entrypoint: 'task.js', + files: [ + 'task.js', + 'task.css', ], - "origin": "Qm...", - "primaryColor": "#e87e23", - "secondaryColor": "#fffaf5", - "standalone": true, - "type": "dapp" + origin: 'Qm...', + primaryColor: '#e87e23', + secondaryColor: '#fffaf5', + standalone: true, + type: 'dapp', }, }; -/* tslint:enable:quotemark */ const sampleKey = '346c22768f84f3050f5c94cec98349b3c5cbfa0b7315304e13647a49181fd1ef'; let description: Description; let testAddressFoo; @@ -84,7 +78,7 @@ let dfs; let dc: DataContract; let nameResolver: NameResolver; -describe('Description handler', function() { +describe('Description handler', function test() { this.timeout(300000); before(async () => { @@ -99,7 +93,7 @@ describe('Description handler', function() { sharing = new Sharing({ contractLoader: await TestUtils.getContractLoader(web3), cryptoProvider, - description: description, + description, executor: await TestUtils.getExecutor(web3), dfs, keyProvider: TestUtils.getKeyProvider(), @@ -121,8 +115,8 @@ describe('Description handler', function() { businessCenterDomain = nameResolver.getDomainName(config.nameResolver.domains.businessCenter); const businessCenterAddress = await nameResolver.getAddress(businessCenterDomain); const businessCenter = await loader.loadContract('BusinessCenter', businessCenterAddress); - if (!await executor.executeContractCall(businessCenter, 'isMember', accounts[0], { from: accounts[0], })) { - await executor.executeContractTransaction(businessCenter, 'join', { from: accounts[0], autoGas: 1.1, }); + if (!await executor.executeContractCall(businessCenter, 'isMember', accounts[0], { from: accounts[0] })) { + await executor.executeContractTransaction(businessCenter, 'join', { from: accounts[0], autoGas: 1.1 }); } testAddressFoo = `${testAddressPrefix}.${nameResolver.getDomainName(config.nameResolver.domains.root)}`; @@ -130,54 +124,68 @@ describe('Description handler', function() { describe('when validing used description', () => { it('should allow valid description', async () => { - const contract = await executor.createContract('Described', [], {from: accounts[0], gas: 1000000, }); - const descriptionEnvelope = { public: Object.assign({}, sampleDescription), }; - await description.setDescriptionToContract(contract.options.address, descriptionEnvelope, accounts[0]); + const contract = await executor.createContract('Described', [], { from: accounts[0], gas: 1000000 }); + const descriptionEnvelope = { public: { ...sampleDescription } }; + await description.setDescriptionToContract( + contract.options.address, descriptionEnvelope, accounts[0], + ); }); it('should reject invalid description', async () => { - const contract = await executor.createContract('Described', [], {from: accounts[0], gas: 1000000, }); + const contract = await executor.createContract('Described', [], { from: accounts[0], gas: 1000000 }); let descriptionEnvelope; let promise; // missing property - descriptionEnvelope = { public: Object.assign({}, sampleDescription), }; + descriptionEnvelope = { public: { ...sampleDescription } }; delete descriptionEnvelope.public.version; - promise = description.setDescriptionToContract(contract.options.address, descriptionEnvelope, accounts[0]); + promise = description.setDescriptionToContract( + contract.options.address, descriptionEnvelope, accounts[0], + ); await expect(promise).to.be.rejected; // additional property - descriptionEnvelope = { public: Object.assign({}, sampleDescription), }; + descriptionEnvelope = { public: { ...sampleDescription } }; descriptionEnvelope.public.newPropery = 123; - promise = description.setDescriptionToContract(contract.options.address, descriptionEnvelope, accounts[0]); + promise = description.setDescriptionToContract( + contract.options.address, descriptionEnvelope, accounts[0], + ); await expect(promise).to.be.rejected; // wrong type - descriptionEnvelope = { public: Object.assign({}, sampleDescription), }; + descriptionEnvelope = { public: { ...sampleDescription } }; descriptionEnvelope.public.version = 123; - promise = description.setDescriptionToContract(contract.options.address, descriptionEnvelope, accounts[0]); + promise = description.setDescriptionToContract( + contract.options.address, descriptionEnvelope, accounts[0], + ); await expect(promise).to.be.rejected; // additional sub property - descriptionEnvelope = { public: Object.assign({}, sampleDescription), }; - descriptionEnvelope.public.dapp = Object.assign({}, descriptionEnvelope.public.dapp, { newProperty: 123, }); - promise = description.setDescriptionToContract(contract.options.address, descriptionEnvelope, accounts[0]); + descriptionEnvelope = { public: { ...sampleDescription } }; + descriptionEnvelope.public.dapp = { ...descriptionEnvelope.public.dapp, newProperty: 123 }; + promise = description.setDescriptionToContract( + contract.options.address, descriptionEnvelope, accounts[0], + ); await expect(promise).to.be.rejected; }); }); describe('when working with ENS descriptions', () => { it('should be able to set and get unencrypted content for ENS addresses', async () => { - await description.setDescriptionToEns(testAddressFoo, { public: sampleDescription, }, accounts[1]); + await description.setDescriptionToEns( + testAddressFoo, { public: sampleDescription }, accounts[1], + ); const content = await description.getDescriptionFromEns(testAddressFoo); - expect(content).to.deep.eq({ public: sampleDescription, }); + expect(content).to.deep.eq({ public: sampleDescription }); }); it('should be able to set and get unencrypted content for ENS addresses including special characters', async () => { const sampleDescriptionSpecialCharacters = { - public: Object.assign({}, sampleDescription, { name: 'Special Characters !"§$%&/()=?ÜÄÖ', }), + public: { ...sampleDescription, name: 'Special Characters !"§$%&/()=?ÜÄÖ' }, }; - await description.setDescriptionToEns(testAddressFoo, sampleDescriptionSpecialCharacters, accounts[1]); + await description.setDescriptionToEns( + testAddressFoo, sampleDescriptionSpecialCharacters, accounts[1], + ); const content = await description.getDescriptionFromEns(testAddressFoo); expect(content).to.deep.eq(sampleDescriptionSpecialCharacters); }); @@ -185,13 +193,16 @@ describe('Description handler', function() { it('should be able to set and get encrypted content for ENS addresses', async () => { const keyConfig = {}; keyConfig[nameResolver.soliditySha3(accounts[1])] = sampleKey; - const keyProvider = new KeyProvider({keys: keyConfig}); - description.keyProvider = keyProvider; + const keyProvider = new KeyProvider({ keys: keyConfig }); + // eslint-disable-next-line no-param-reassign + (description as any).keyProvider = keyProvider; const cryptor = new Aes(); const cryptoConfig = {}; const cryptoInfo = cryptor.getCryptoInfo(nameResolver.soliditySha3(accounts[1])); - cryptoConfig['aes'] = cryptor; - description.cryptoProvider = new CryptoProvider(cryptoConfig); + // eslint-disable-next-line no-param-reassign + (cryptoConfig as any).aes = cryptor; + // eslint-disable-next-line no-param-reassign + (description as any).cryptoProvider = new CryptoProvider(cryptoConfig); const secureDescription = { public: sampleDescription, private: { @@ -211,18 +222,18 @@ describe('Description handler', function() { const keyConfig = {}; keyConfig[nameResolver.soliditySha3(contract.options.address)] = sampleKey; const keyProvider = new KeyProvider(keyConfig); - description.keyProvider = keyProvider; + (description as any).keyProvider = keyProvider; const cryptor = new Aes(); const cryptoConfig = {}; const cryptoInfo = cryptor.getCryptoInfo(nameResolver.soliditySha3(contract.options.address)); - cryptoConfig['aes'] = cryptor; - description.cryptoProvider = new CryptoProvider(cryptoConfig); + (cryptoConfig as any).aes = cryptor; + (description as any).cryptoProvider = new CryptoProvider(cryptoConfig); const envelope = { - cryptoInfo: cryptoInfo, + cryptoInfo, public: sampleDescription, private: { name: 'real name', - } + }, }; await description .setDescriptionToContract(contract.options.address, envelope, accounts[0]); @@ -233,18 +244,18 @@ describe('Description handler', function() { const keyConfig = {}; keyConfig[nameResolver.soliditySha3(contract.options.address)] = sampleKey; const keyProvider = new KeyProvider(keyConfig); - description.keyProvider = keyProvider; + (description as any).keyProvider = keyProvider; const cryptor = new Aes(); const cryptoConfig = {}; const cryptoInfo = cryptor.getCryptoInfo(nameResolver.soliditySha3(contract.options.address)); - cryptoConfig['aes'] = cryptor; - description.cryptoProvider = new CryptoProvider(cryptoConfig); + (cryptoConfig as any).aes = cryptor; + (description as any).cryptoProvider = new CryptoProvider(cryptoConfig); const envelope = { - cryptoInfo: cryptoInfo, + cryptoInfo, public: sampleDescription, private: { name: 'real name', - } + }, }; await description .setDescriptionToContract(contract.options.address, envelope, accounts[0]); diff --git a/src/shared-description.ts b/src/shared-description.ts index a5bfc2fc..e74e7f11 100644 --- a/src/shared-description.ts +++ b/src/shared-description.ts @@ -17,20 +17,19 @@ the following URL: https://evan.network/license/ */ -import { Envelope } from '@evan.network/dbcp'; - import * as Dbcp from '@evan.network/dbcp'; import { Sharing } from './contracts/sharing'; + export interface DescriptionOptions extends Dbcp.DescriptionOptions { - sharing: Sharing, + sharing: Sharing; } export class Description extends Dbcp.Description { - sharing: Sharing; + public sharing: Sharing; - constructor(options: DescriptionOptions) { + public constructor(options: DescriptionOptions) { super(options); this.sharing = options.sharing; } @@ -41,7 +40,10 @@ export class Description extends Dbcp.Description { * @param {string} ensAddress The ens address where the description is stored * @return {Envelope} description as an Envelope */ - async getDescriptionFromContract(contractAddress: string, accountId: string): Promise { + public async getDescriptionFromContract( + contractAddress: string, + accountId: string, + ): Promise { let result = null; const contract = this.contractLoader.loadContract('Described', contractAddress); const hash = await this.executor.executeContractCall(contract, 'contractDescription'); @@ -52,11 +54,14 @@ export class Description extends Dbcp.Description { if (this.sharing) { try { const cryptor = this.cryptoProvider.getCryptorByCryptoInfo(result.cryptoInfo); - const sharingKey = await this.sharing.getKey(contractAddress, accountId, '*', result.cryptoInfo.block); + const sharingKey = await this.sharing.getKey( + contractAddress, accountId, '*', result.cryptoInfo.block, + ); const key = sharingKey; const privateData = await cryptor.decrypt( - Buffer.from(result.private, this.encodingEncrypted as BufferEncoding), { key, }); - result.private = privateData; + Buffer.from(result.private, this.encodingEncrypted as BufferEncoding), { key }, + ); + result.private = privateData; } catch (e) { result.private = new Error('wrong_key'); } @@ -66,7 +71,7 @@ export class Description extends Dbcp.Description { } } return result; - }; + } /** @@ -74,17 +79,21 @@ export class Description extends Dbcp.Description { * * @param {string} contractAddress The contract address where description will be * stored - * @param {Envelope|string} envelope description as an envelope or a presaved description hash + * @param {Envelope|string} envelope description as an envelope or a presaved + * description hash * @param {string} accountId ETH account id - * @return {Promise} resolved when done + * @return {Promise} resolved when done */ - async setDescriptionToContract(contractAddress: string, envelope: Envelope|string, accountId: string): - Promise { + public async setDescriptionToContract( + contractAddress: string, + envelope: Dbcp.Envelope|string, + accountId: string, + ): Promise { let hash; if (typeof envelope === 'string') { hash = envelope; } else { - const content: Envelope = Object.assign({}, envelope); + const content: Dbcp.Envelope = { ...envelope }; // add dbcp version if (!content.public.dbcpVersion) { this.log('dbcpVersion not set, using fallback of version 1', 'warning'); @@ -99,14 +108,18 @@ export class Description extends Dbcp.Description { const blockNr = await this.web3.eth.getBlockNumber(); const sharingKey = await this.sharing.getKey(contractAddress, accountId, '*', blockNr); const key = sharingKey; - const encrypted = await cryptor.encrypt(content.private, { key, }); + const encrypted = await cryptor.encrypt(content.private, { key }); content.private = encrypted.toString(this.encodingEncrypted); content.cryptoInfo.block = blockNr; } hash = await this.dfs.add( - 'description', Buffer.from(JSON.stringify(content), this.encodingEnvelope as BufferEncoding)); + 'description', + Buffer.from(JSON.stringify(content), this.encodingEnvelope as BufferEncoding), + ); } const contract = this.contractLoader.loadContract('Described', contractAddress); - await this.executor.executeContractTransaction(contract, 'setContractDescription', {from: accountId, gas: 200000}, hash); - }; + await this.executor.executeContractTransaction( + contract, 'setContractDescription', { from: accountId, gas: 200000 }, hash, + ); + } } diff --git a/src/test/accounts.ts b/src/test/accounts.ts index 5019fe47..61446156 100644 --- a/src/test/accounts.ts +++ b/src/test/accounts.ts @@ -24,6 +24,8 @@ const accountMap1 = { '7d09c0873e3f8dc0c7282bb7c2ba76bfd432bff53c38ace06193d1e4faa977e7', '0x00D1267B27C3A80080f9E1B6Ba01DE313b53Ab58': 'a76a2b068fb715830d042ca40b1a4dab8d088b217d11af91d15b972a7afaf202', + '0x0ab4F29ef71E591e209b1386CaDFc5B7CCB5102A': + '70adb5e0424148e2b490776143a6a93662d3f40c3d9597690bdd3472863b7625', }; const accountMap2 = { @@ -33,7 +35,7 @@ const accountMap2 = { 'D9734AFE9168C37481A977C91FE25B9C7D814789F515D78DC084A27BD2137E14', '0x04B1Ee1b9D5283B2694B739DA5b49DBC88199750': '68475374AC69364D64F94A47D66410936F63971FE5EEAEFDF85913D153799EE5', -} +}; const accountMap3 = { '0xC2ee94f6cf046B02D530cf1cd16A2b32b8A4340d': @@ -42,20 +44,20 @@ const accountMap3 = { 'E29C1E4A683CC629E39CE219CFB1F35BBA898605E1B197162F0EECF0F1139630', '0x35f8220bC83577458aEa4a1085A8b832DEa79b7a': '340BA316637FD01A1AFD54D4491A899F6D8EA0FB89A1D7BA94682F7D68B21B20', -} - +}; +// eslint-disable-next-line let accountMap; -if (process.env && process.env.ACCOUNT_MAP) { +if (process.env && process.env.ACCOUNT_MAP) { accountMap = JSON.parse(process.env.ACCOUNT_MAP); -} else if (process.env && process.env.TESTSPEC === 'contracts') { - accountMap = accountMap1; -} else if (process.env && process.env.TESTSPEC === 'datacontract') { - accountMap = accountMap2; -} else if (process.env && process.env.TESTSPEC === 'services') { - accountMap = accountMap3; +} else if (process.env && process.env.TESTSPEC === 'contracts') { + accountMap = accountMap1; +} else if (process.env && process.env.TESTSPEC === 'datacontract') { + accountMap = accountMap2; +} else if (process.env && process.env.TESTSPEC === 'services') { + accountMap = accountMap3; } else { - accountMap = accountMap1; + accountMap = accountMap1; } -let accounts = Object.keys(accountMap); +const accounts = Object.keys(accountMap); -export { accounts, accountMap } +export { accounts, accountMap }; diff --git a/src/test/test-utils.ts b/src/test/test-utils.ts index 01a0bc47..21fbf4b7 100644 --- a/src/test/test-utils.ts +++ b/src/test/test-utils.ts @@ -17,101 +17,91 @@ the following URL: https://evan.network/license/ */ -import crypto = require('crypto'); -import smartContract = require('@evan.network/smart-contracts-core'); -const Web3 = require('web3'); +import * as Web3 from 'web3'; +import { accountMap, accounts } from './accounts'; +import { configTestcore as config } from '../config-testcore'; import { AccountStore, + Aes, + AesBlob, + AesEcb, + BaseContract, ContractLoader, + CryptoProvider, + DataContract, + Description, DfsInterface, + Did, + EncryptionWrapper, EventHub, Executor, + ExecutorWallet, + Ipfs, + Ipld, KeyProvider, Logger, + NameResolver, + Payments, + Profile, + RightsAndRoles, + ServiceContract, + Sharing, + SignerIdentity, SignerInternal, Unencrypted, -} from '@evan.network/dbcp'; - -import { accountMap } from './accounts'; -import { accounts } from './accounts'; -import { createDefaultRuntime, Runtime } from '../index'; -import { Aes } from '../encryption/aes'; -import { AesBlob } from '../encryption/aes-blob'; -import { AesEcb } from '../encryption/aes-ecb'; -import { BaseContract } from '../contracts/base-contract/base-contract'; -import { Verifications } from '../verifications/verifications'; -import { configTestcore as config } from './../config-testcore'; -import { CryptoProvider } from '../encryption/crypto-provider'; -import { DataContract } from '../contracts/data-contract/data-contract'; -import { EncryptionWrapper } from '../encryption/encryption-wrapper'; -import { ExecutorWallet } from '../contracts/executor-wallet'; -import { Ipld } from '../dfs/ipld'; -import { Ipfs } from '../dfs/ipfs'; -import { NameResolver } from '../name-resolver'; -import { Payments } from '../payments'; -import { Profile } from '../profile/profile'; -import { RightsAndRoles } from '../contracts/rights-and-roles'; -import { ServiceContract } from '../contracts/service-contract/service-contract'; -import { setTimeout } from 'timers'; -import { Description } from '../shared-description'; -import { Sharing } from '../contracts/sharing'; -import { Votings } from '../votings/votings'; -import { Wallet } from '../contracts/wallet'; + Verifications, + Votings, + Wallet, +} from '../index'; +import { + Runtime, + createDefaultRuntime, +} from '../runtime'; + +import crypto = require('crypto'); +import smartContract = require('@evan.network/smart-contracts-core'); export const publicMailBoxExchange = 'mailboxKeyExchange'; export const sampleContext = 'context sample'; -const web3Provider = process.env.CHAIN_ENDPOINT || 'wss://testcore.evan.network/ws'; -// const wsp = new Web3.providers.WebsocketProvider( -// web3Provider, { clientConfig: { keepalive: true, keepaliveInterval: 5000 } }); -const localWeb3 = new Web3(web3Provider, null, { transactionConfirmationBlocks: 1 }); +const web3Provider = (process.env.CHAIN_ENDPOINT as any) || 'wss://testcore.evan.network/ws'; +// due to issues with typings in web3 remove type from Web3 +const localWeb3 = new (Web3 as any)(web3Provider, null, { transactionConfirmationBlocks: 1 }); const sampleKeys = {}; // dataKeys -sampleKeys[localWeb3.utils.soliditySha3(accounts[0])] = - '001de828935e8c7e4cb56fe610495cae63fb2612000000000000000000000000'; // plain acc0 key -sampleKeys[localWeb3.utils.soliditySha3(accounts[1])] = - '0030c5e7394585400b1fb193ddbcb45a37ab916e000000000000000000000011'; // plain acc1 key -sampleKeys[localWeb3.utils.soliditySha3(sampleContext)] = - '00000000000000000000000000000000000000000000000000000000005a3973'; -sampleKeys[localWeb3.utils.soliditySha3(publicMailBoxExchange)] = - '346c22768f84f3050f5c94cec98349b3c5cbfa0b7315304e13647a4918ffff22'; // accX <--> mailbox edge key -sampleKeys[localWeb3.utils.soliditySha3('wulfwulf.test')] = - '00000000000000000000000000000000000000000000000000000000005a3973'; -sampleKeys[localWeb3.utils.soliditySha3(accounts[2])] = - '00d1267b27c3a80080f9e1b6ba01de313b53ab58000000000000000000000022'; +sampleKeys[localWeb3.utils.soliditySha3(accounts[0])] = '001de828935e8c7e4cb56fe610495cae63fb2612000000000000000000000000'; // plain acc0 key +sampleKeys[localWeb3.utils.soliditySha3(accounts[1])] = '0030c5e7394585400b1fb193ddbcb45a37ab916e000000000000000000000011'; // plain acc1 key +sampleKeys[localWeb3.utils.soliditySha3(sampleContext)] = '00000000000000000000000000000000000000000000000000000000005a3973'; +sampleKeys[localWeb3.utils.soliditySha3(publicMailBoxExchange)] = '346c22768f84f3050f5c94cec98349b3c5cbfa0b7315304e13647a4918ffff22'; // accX <--> mailbox edge key +sampleKeys[localWeb3.utils.soliditySha3('wulfwulf.test')] = '00000000000000000000000000000000000000000000000000000000005a3973'; +sampleKeys[localWeb3.utils.soliditySha3(accounts[2])] = '00d1267b27c3a80080f9e1b6ba01de313b53ab58000000000000000000000022'; +sampleKeys[localWeb3.utils.soliditySha3(accounts[3])] = '483257531bc9456ea783e44d325f8a384a4b89da81dac00e589409431692f218'; // commKeys sampleKeys[localWeb3.utils.soliditySha3.apply(localWeb3.utils.soliditySha3, - [localWeb3.utils.soliditySha3(accounts[0]), localWeb3.utils.soliditySha3(accounts[0])].sort())] = - '001de828935e8c7e4cb56fe610495cae63fb2612000000000000000000000000'; // acc0 <--> acc0 edge key + [localWeb3.utils.soliditySha3(accounts[0]), localWeb3.utils.soliditySha3(accounts[0])].sort())] = '001de828935e8c7e4cb56fe610495cae63fb2612000000000000000000000000'; // acc0 <--> acc0 edge key sampleKeys[localWeb3.utils.soliditySha3.apply(localWeb3.utils.soliditySha3, - [localWeb3.utils.soliditySha3(accounts[0]), localWeb3.utils.soliditySha3(accounts[1])].sort())] = - '001de828935e8c7e4cb50030c5e7394585400b1f000000000000000000000001'; // acc0 <--> acc1 edge key + [localWeb3.utils.soliditySha3(accounts[0]), localWeb3.utils.soliditySha3(accounts[1])].sort())] = '001de828935e8c7e4cb50030c5e7394585400b1f000000000000000000000001'; // acc0 <--> acc1 edge key sampleKeys[localWeb3.utils.soliditySha3.apply(localWeb3.utils.soliditySha3, - [localWeb3.utils.soliditySha3(accounts[0]), localWeb3.utils.soliditySha3(accounts[2])].sort())] = - '001de828935e8c7e4cb500d1267b27c3a80080f9000000000000000000000002'; // acc0 <--> acc1 edge key + [localWeb3.utils.soliditySha3(accounts[0]), localWeb3.utils.soliditySha3(accounts[2])].sort())] = '001de828935e8c7e4cb500d1267b27c3a80080f9000000000000000000000002'; // acc0 <--> acc1 edge key sampleKeys[localWeb3.utils.soliditySha3.apply(localWeb3.utils.soliditySha3, - [localWeb3.utils.soliditySha3(accounts[1]), localWeb3.utils.soliditySha3(accounts[1])].sort())] = - '0030c5e7394585400b1fb193ddbcb45a37ab916e000000000000000000000011'; + [localWeb3.utils.soliditySha3(accounts[1]), localWeb3.utils.soliditySha3(accounts[1])].sort())] = '0030c5e7394585400b1fb193ddbcb45a37ab916e000000000000000000000011'; sampleKeys[localWeb3.utils.soliditySha3.apply(localWeb3.utils.soliditySha3, - [localWeb3.utils.soliditySha3(accounts[1]), localWeb3.utils.soliditySha3(accounts[2])].sort())] = - '0030c5e7394585400b1f00d1267b27c3a80080f9000000000000000000000012'; // acc1 <--> acc2 edge key + [localWeb3.utils.soliditySha3(accounts[1]), localWeb3.utils.soliditySha3(accounts[2])].sort())] = '0030c5e7394585400b1f00d1267b27c3a80080f9000000000000000000000012'; // acc1 <--> acc2 edge key sampleKeys[localWeb3.utils.soliditySha3.apply(localWeb3.utils.soliditySha3, - [localWeb3.utils.soliditySha3(accounts[2]), localWeb3.utils.soliditySha3(accounts[2])].sort())] = - '00d1267b27c3a80080f9e1b6ba01de313b53ab58000000000000000000000022'; + [localWeb3.utils.soliditySha3(accounts[2]), localWeb3.utils.soliditySha3(accounts[2])].sort())] = '00d1267b27c3a80080f9e1b6ba01de313b53ab58000000000000000000000022'; sampleKeys[localWeb3.utils.soliditySha3.apply(localWeb3.utils.soliditySha3, - [localWeb3.utils.soliditySha3('0xA1d67A22eA4B4B3e46741E86f1007A5082D99842'), localWeb3.utils.soliditySha3('0xA1d67A22eA4B4B3e46741E86f1007A5082D99842')].sort())] = - '676d0c639099a29b9dd21fe3af7a4c159df73b7068394393524c13679218f29f'; + [localWeb3.utils.soliditySha3(accounts[3]), localWeb3.utils.soliditySha3(accounts[3])].sort())] = '483257531bc9456ea783e44d325f8a384a4b89da81dac00e589409431692f218'; export class TestUtils { - static getAccountStore(options): AccountStore { - return new AccountStore({ accounts: accountMap, }); + public static getAccountStore(): AccountStore { + return new AccountStore({ accounts: accountMap }); } - static async getBaseContract(web3): Promise { + public static async getBaseContract(web3): Promise { const eventHub = await this.getEventHub(web3); const executor = await this.getExecutor(web3); executor.eventHub = eventHub; @@ -121,40 +111,24 @@ export class TestUtils { log: Logger.getDefaultLog(), nameResolver: await TestUtils.getNameResolver(web3), }); - }; - - static async getVerifications(web3, dfs, requestedKeys?: string[]): Promise { - const eventHub = await this.getEventHub(web3); - const executor = await this.getExecutor(web3); - executor.eventHub = eventHub; - return new Verifications({ - config, - contractLoader: await TestUtils.getContractLoader(web3), - description: await TestUtils.getDescription(web3, dfs, requestedKeys), - executor, - nameResolver: await this.getNameResolver(web3), - accountStore: this.getAccountStore({}), - dfs - }); } - static getConfig(): any { + public static getConfig(): any { return config; } - static async getContractLoader(web3): Promise { + public static async getContractLoader(web3): Promise { const contracts = await this.getContracts(); return new ContractLoader({ contracts, - web3 + web3, }); } - static async getContracts() { - + public static async getContracts() { const solc = new smartContract.Solc({ log: Logger.getDefaultLog(), - config: { compileContracts: false, }, + config: { compileContracts: false }, }); await solc.ensureCompiled(); const contracts = solc.getContracts(); @@ -162,21 +136,20 @@ export class TestUtils { return contracts; } - static getCryptoProvider(dfs?: any) { + public static getCryptoProvider(dfs?: any) { const cryptor = new Aes(); const unencryptedCryptor = new Unencrypted(); const cryptoConfig = {}; - const cryptoInfo = cryptor.getCryptoInfo(localWeb3.utils.soliditySha3(accounts[0])); - cryptoConfig['aes'] = cryptor; - cryptoConfig['aesEcb'] = new AesEcb(); - cryptoConfig['unencrypted'] = unencryptedCryptor; + (cryptoConfig as any).aes = cryptor; + (cryptoConfig as any).aesEcb = new AesEcb(); + (cryptoConfig as any).unencrypted = unencryptedCryptor; if (dfs) { - cryptoConfig['aesBlob'] = new AesBlob({ dfs }); + (cryptoConfig as any).aesBlob = new AesBlob({ dfs }); } return new CryptoProvider(cryptoConfig); } - static async getDataContract(web3, dfs, requestedKeys?: string[]) { + public static async getDataContract(web3, dfs, requestedKeys?: string[]) { const sharing = await this.getSharing(web3, dfs, requestedKeys); const description = await this.getDescription(web3, dfs, requestedKeys); description.sharing = sharing; @@ -196,12 +169,15 @@ export class TestUtils { }); } - static async getDescription(web3, dfsParam?: DfsInterface, requestedKeys?: string[]): Promise { + public static async getDescription( + web3, + dfsParam?: DfsInterface, + requestedKeys?: string[], + ): Promise { const executor = await this.getExecutor(web3); - const contracts = await this.getContracts(); const contractLoader = await this.getContractLoader(web3); const dfs = dfsParam || await this.getIpfs(); - const nameResolver = await this.getNameResolver(web3); + const nameResolver = await this.getNameResolver(web3); const cryptoProvider = this.getCryptoProvider(); return new Description({ contractLoader, @@ -215,7 +191,27 @@ export class TestUtils { }); } - static async getEncryptionWrapper(web3: any, dfs: DfsInterface, requestedKeys?: string[] + public static async getDid(web3: any, accountId?: string, dfs?: any): Promise { + const signerIdentity = await this.getSignerIdentity(web3, accountId); + const executor = new Executor( + { config: { alwaysAutoGasLimit: 1.1 }, signer: signerIdentity, web3 }, + ); + await executor.init({ eventHub: await TestUtils.getEventHub(web3) }); + + return new Did({ + contractLoader: await this.getContractLoader(web3), + dfs: dfs || (await this.getIpfs()), + executor, + nameResolver: await this.getNameResolver(web3), + signerIdentity, + web3, + }); + } + + public static async getEncryptionWrapper( + web3: any, + dfs: DfsInterface, + requestedKeys?: string[], ): Promise { return new EncryptionWrapper({ cryptoProvider: this.getCryptoProvider(), @@ -226,7 +222,7 @@ export class TestUtils { }); } - static async getEventHub(web3): Promise { + public static async getEventHub(web3): Promise { return new EventHub({ config: config.nameResolver, contractLoader: await this.getContractLoader(web3), @@ -235,76 +231,53 @@ export class TestUtils { }); } - static async getExecutor(web3, isReadonly?): Promise { + public static async getExecutor(web3: any, isReadonly = false): Promise { if (isReadonly) { return new Executor({}); - } else { - const contracts = await this.getContracts(); - const contractLoader = new ContractLoader({ - contracts, - web3, - }); - const accountStore = this.getAccountStore({}); - const signer = new SignerInternal({ - accountStore, - contractLoader, - config: {}, - web3, - }); - const executor = new Executor({ config, signer, web3, }); - await executor.init({}); - - return executor; } - } - - static async getExecutorWallet(web3, wallet, accountId, dfsParam?: DfsInterface): Promise { const contracts = await this.getContracts(); - const contractLoader = new ContractLoader({ + const contractLoader = new ContractLoader({ contracts, web3, }); - const accountStore = this.getAccountStore({}); + const accountStore = this.getAccountStore(); const signer = new SignerInternal({ accountStore, contractLoader, config: {}, web3, }); - const executor = new ExecutorWallet({ accountId, config, contractLoader, signer, wallet, web3, }); + const executor = new Executor({ config, signer, web3 }); await executor.init({}); return executor; } - static async getIpld(_ipfs?: Ipfs, _keyProvider?: KeyProvider): Promise { - const cryptor = new Aes(); - const key = await cryptor.generateKey(); - const ipfs = _ipfs ? _ipfs : await this.getIpfs(); - const nameResolver = await this.getNameResolver(await this.getWeb3()); - return new Promise((resolve) => { - // crypto provider - const cryptoConfig = {}; - const cryptoInfo = cryptor.getCryptoInfo(localWeb3.utils.soliditySha3(accounts[0])); - const cryptoProvider = this.getCryptoProvider(); - // key provider - const keyProvider = _keyProvider || (new KeyProvider({ keys: sampleKeys, })); - - resolve(new Ipld({ - ipfs, - keyProvider, - cryptoProvider, - defaultCryptoAlgo: 'aes', - originator: nameResolver.soliditySha3(accounts[0]), - nameResolver, - })) + public static async getExecutorWallet(web3, wallet, accountId): Promise { + const contracts = await this.getContracts(); + const contractLoader = new ContractLoader({ + contracts, + web3, + }); + const accountStore = this.getAccountStore(); + const signer = new SignerInternal({ + accountStore, + contractLoader, + config: {}, + web3, + }); + const executor = new ExecutorWallet({ + accountId, config, contractLoader, signer, wallet, web3, }); + await executor.init({}); + + return executor; } - static async getIpfs(): Promise { + public static async getIpfs(): Promise { const contracts = await this.getContracts(); - const accountStore = this.getAccountStore({}); - const contractLoader = new ContractLoader({ + const accountStore = this.getAccountStore(); + const contractLoader = new ContractLoader({ contracts, web3: this.getWeb3(), }); @@ -315,14 +288,34 @@ export class TestUtils { web3: this.getWeb3(), }); const ipfs = new Ipfs({ - dfsConfig: {host: 'ipfs.test.evan.network', port: '443', protocol: 'https'}, - disablePin: true + dfsConfig: { host: 'ipfs.test.evan.network', port: '443', protocol: 'https' }, + disablePin: true, }); ipfs.setRuntime({ signer, activeAccount: accounts[0], web3: this.getWeb3() }); return ipfs; } - static getKeyProvider(requestedKeys?: string[]) { + public static async getIpld(_ipfs?: Ipfs, _keyProvider?: KeyProvider): Promise { + const ipfs = _ipfs || await this.getIpfs(); + const nameResolver = await this.getNameResolver(await this.getWeb3()); + return new Promise((resolve) => { + // crypto provider + const cryptoProvider = this.getCryptoProvider(); + // key provider + const keyProvider = _keyProvider || (new KeyProvider({ keys: sampleKeys })); + + resolve(new Ipld({ + ipfs, + keyProvider, + cryptoProvider, + defaultCryptoAlgo: 'aes', + originator: nameResolver.soliditySha3(accounts[0]), + nameResolver, + })); + }); + } + + public static getKeyProvider(requestedKeys?: string[]) { let keys; if (!requestedKeys) { keys = sampleKeys; @@ -332,20 +325,20 @@ export class TestUtils { keys[key] = sampleKeys[key]; }); } - return new KeyProvider({ keys, }); + return new KeyProvider({ keys }); } - static getKeys(): any { + public static getKeys(): any { return sampleKeys; } - static getLogger(): Function { + public static getLogger(): Function { return Logger.getDefaultLog(); } - static async getNameResolver(web3): Promise { + public static async getNameResolver(web3): Promise { const contracts = await this.getContracts(); - const contractLoader = new ContractLoader({ + const contractLoader = new ContractLoader({ contracts, web3, }); @@ -360,13 +353,17 @@ export class TestUtils { return nameResolver; } - static async getPayments(web3, accountId): Promise { + public static async nextBlock(executor: Executor, accoutId: string): Promise { + await executor.executeSend({ from: accoutId, value: 0, to: accoutId }); + } + + public static async getPayments(web3): Promise { const executor = await TestUtils.getExecutor(web3); const eventHub = await TestUtils.getEventHub(web3); executor.eventHub = eventHub; const payments = new Payments({ web3, - accountStore: this.getAccountStore({}), + accountStore: this.getAccountStore(), contractLoader: await TestUtils.getContractLoader(web3), executor, }); @@ -374,7 +371,7 @@ export class TestUtils { return payments; } - static async getProfile(web3, ipfs?, ipld?, accountId?): Promise { + public static async getProfile(web3, ipfs?, ipld?, accountId?): Promise { const executor = await TestUtils.getExecutor(web3); const dfs = ipfs || await TestUtils.getIpfs(); executor.eventHub = await TestUtils.getEventHub(web3); @@ -397,15 +394,24 @@ export class TestUtils { return profile; } - static getRandomAddress(): string { + public static getRandomAddress(): string { return localWeb3.utils.toChecksumAddress(`0x${crypto.randomBytes(20).toString('hex')}`); } - static getRandomBytes32(): string { + public static getRandomBytes32(): string { return `0x${crypto.randomBytes(32).toString('hex')}`; } - public static async getRuntime(accountId, requestedKeys?): Promise { + public static async getRightsAndRoles(web3) { + return new RightsAndRoles({ + contractLoader: await TestUtils.getContractLoader(web3), + executor: await TestUtils.getExecutor(web3), + nameResolver: await TestUtils.getNameResolver(web3), + web3, + }); + } + + public static async getRuntime(accountId, requestedKeys?, customConfig = {}): Promise { let keys; if (!requestedKeys) { keys = sampleKeys; @@ -421,20 +427,12 @@ export class TestUtils { { accountMap: { [accountId]: accountMap[accountId] }, keyConfig: keys, - } + ...customConfig, + }, ); } - static async getRightsAndRoles(web3) { - return new RightsAndRoles({ - contractLoader: await TestUtils.getContractLoader(web3), - executor: await TestUtils.getExecutor(web3) , - nameResolver: await TestUtils.getNameResolver(web3), - web3, - }); - } - - static async getServiceContract(web3, ipfs?: Ipfs, keyProvider?: KeyProvider) { + public static async getServiceContract(web3, ipfs?: Ipfs, keyProvider?: KeyProvider) { const executor = await TestUtils.getExecutor(web3); executor.eventHub = await TestUtils.getEventHub(web3); const dfs = ipfs || await TestUtils.getIpfs(); @@ -451,8 +449,12 @@ export class TestUtils { }); } - static async getSharing(web3, dfsParam?: DfsInterface, requestedKeys?: string[]): Promise { - const dfs = dfsParam ? dfsParam : await TestUtils.getIpfs(); + public static async getSharing( + web3, + dfsParam?: DfsInterface, + requestedKeys?: string[], + ): Promise { + const dfs = dfsParam || await TestUtils.getIpfs(); return new Sharing({ contractLoader: await TestUtils.getContractLoader(web3), cryptoProvider: TestUtils.getCryptoProvider(), @@ -465,7 +467,57 @@ export class TestUtils { }); } - static async getVotings(web3): Promise { + public static async getSignerIdentity( + web3: any, + accountId = accounts[0], + ): Promise { + const contracts = await TestUtils.getContracts(); + const contractLoader = new ContractLoader({ + contracts, + web3, + }); + const accountStore = TestUtils.getAccountStore(); + const verifications = await TestUtils.getVerifications(web3, await TestUtils.getIpfs()); + const underlyingSigner = new SignerInternal({ + accountStore, + contractLoader, + config: {}, + web3, + }); + return new SignerIdentity( + { + contractLoader, + verifications, + web3, + }, + { + activeIdentity: await verifications.getIdentityForAccount(accountId, true), + underlyingAccount: accountId, + underlyingSigner, + }, + ); + } + + public static async getVerifications( + web3, + dfs?, + requestedKeys?: string[], + ): Promise { + const eventHub = await this.getEventHub(web3); + const executor = await this.getExecutor(web3); + executor.eventHub = eventHub; + return new Verifications({ + config, + contractLoader: await TestUtils.getContractLoader(web3), + description: await TestUtils.getDescription(web3, dfs, requestedKeys), + executor, + nameResolver: await this.getNameResolver(web3), + accountStore: this.getAccountStore(), + dfs: dfs || (await this.getIpfs()), + }); + } + + public static async getVotings(web3): Promise { const executor = await TestUtils.getExecutor(web3); executor.eventHub = await TestUtils.getEventHub(web3); return new Votings({ @@ -475,8 +527,8 @@ export class TestUtils { }); } - static async getWallet(web3, dfsParam?: DfsInterface): Promise { - const dfs = dfsParam ? dfsParam : await TestUtils.getIpfs(); + public static async getWallet(web3, dfsParam?: DfsInterface): Promise { + const dfs = dfsParam || await TestUtils.getIpfs(); const executor = await TestUtils.getExecutor(web3); executor.eventHub = await TestUtils.getEventHub(web3); return new Wallet({ @@ -488,16 +540,12 @@ export class TestUtils { }); } - static getWeb3(provider = web3Provider) { + public static getWeb3() { // connect to web3 return localWeb3; } - static async nextBlock(executor: Executor, accoutId: string): Promise { - await executor.executeSend({ from: accoutId, value: 0, to: accoutId }); - }; - - static async sleep(ms): Promise { - await new Promise(s => setTimeout(() => s(), ms)); + public static async sleep(ms): Promise { + await new Promise((s) => setTimeout(() => s(), ms)); } } diff --git a/src/vc/vc.spec.ts b/src/vc/vc.spec.ts new file mode 100644 index 00000000..0f43bef6 --- /dev/null +++ b/src/vc/vc.spec.ts @@ -0,0 +1,389 @@ +/* + Copyright (C) 2018-present evan GmbH. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License, version 3, + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see http://www.gnu.org/licenses/ or + write to the Free Software Foundation, Inc., 51 Franklin Street, + Fifth Floor, Boston, MA, 02110-1301 USA, or download the license from + the following URL: https://evan.network/license/ +*/ + +import * as chaiAsPromised from 'chai-as-promised'; +import * as didJWT from 'did-jwt'; +import { expect, use } from 'chai'; + +import { Runtime } from '../index'; +import { TestUtils } from '../test/test-utils'; +import { VcDocumentTemplate, VcDocument } from './vc'; +import { Verifications } from '../verifications/verifications'; +import { accounts } from '../test/accounts'; + +use(chaiAsPromised); + +// eslint-disable-next-line func-names +describe('VC Resolver', function () { + this.timeout(600000); + let runtime: Runtime; + let verifications: Verifications; + const issuerAccountId = accounts[0]; + const subjectAccountId = accounts[1]; + let issuerIdentityId; + let subjectIdentityId; + + let minimalValidVcData: VcDocumentTemplate; + + // Mock the did-resolver package that did-jwt usually requires + const evanResolver = { + async resolve(did) { + return (await runtime.did.getDidDocument(did)) as any; + }, + }; + + async function compareDocuments(original: VcDocument, signedPayload: any): Promise { + const sig = { + ...signedPayload, + }; + const orig = { + ...original, + }; + delete sig.proof; + delete orig.proof; + const proofPayloadHash = await runtime.nameResolver.soliditySha3(JSON.stringify(sig)); + const documentHash = await runtime.nameResolver.soliditySha3(JSON.stringify(orig)); + expect(proofPayloadHash).eq(documentHash); + } + + before(async () => { + runtime = await TestUtils.getRuntime(accounts[0], null, { useIdentity: true }); + verifications = await TestUtils.getVerifications(runtime.web3, await TestUtils.getIpfs()); + issuerIdentityId = await verifications.getIdentityForAccount(issuerAccountId, true); + subjectIdentityId = await verifications.getIdentityForAccount(subjectAccountId, true); + minimalValidVcData = { + issuer: { + id: await runtime.did.convertIdentityToDid(issuerIdentityId), + }, + credentialSubject: { + id: await runtime.did.convertIdentityToDid(subjectIdentityId), + data: [ + { + name: 'isTrustedSupplier', + value: 'true', + }, + ], + }, + validFrom: new Date(Date.now()).toISOString(), + }; + }); + + describe('When creating a VC', async () => { + it('allows me to create a valid offline VC', async () => { + const myDoc: VcDocumentTemplate = { + ...minimalValidVcData, + id: 'randomCustomId', + }; + const createdVcDoc = await runtime.vc.createVc(myDoc); + + expect(createdVcDoc.id).to.eq(myDoc.id); + expect(createdVcDoc.issuer.id).to.eq(myDoc.issuer.id); + expect(createdVcDoc.credentialSubject.id).to.eq(myDoc.credentialSubject.id); + await expect(didJWT.verifyJWT(createdVcDoc.proof.jws, { resolver: evanResolver })) + .to.be.eventually.fulfilled; + + await expect(didJWT.verifyJWT(createdVcDoc.proof.jws, { resolver: evanResolver })) + .to.not.be.rejected; + const verifiedSignature = await didJWT.verifyJWT( + createdVcDoc.proof.jws, + { resolver: evanResolver }, + ); + + // fails if signed VC and proof-carrying VC not matching + await compareDocuments(createdVcDoc, verifiedSignature.payload.vc); + }); + + it('does not allow me to issue a VC with missing issuer ID', async () => { + const promise = runtime.vc.createVc(minimalValidVcData); + await expect(promise).to.be.rejectedWith('VC misses id'); + }); + + it('does not allow me to issue a VC under a different issuer ID', async () => { + const myDoc: VcDocumentTemplate = { + ...minimalValidVcData, + id: 'randomCustomId', + issuer: { + id: await runtime.did.convertIdentityToDid(subjectIdentityId), + }, + }; + const promise = runtime.vc.createVc(myDoc); + + await expect(promise).to.be.rejectedWith('You are not authorized to issue this VC'); + }); + + it('does not allow me to create a VC without an issuer', async () => { + const vc = { + ...minimalValidVcData, + id: 'randomCustomId', + issuer: { + id: '', + }, + }; + const promise = runtime.vc.createVc(vc); + + await expect(promise).to.be.rejectedWith(`Invalid issuer DID: ${vc.issuer.id}`); + }); + + it('does not allow me to create a VC without a subject', async () => { + const vc = { + ...minimalValidVcData, + id: 'randomCustomId', + credentialSubject: { + id: '', + }, + }; + const promise = runtime.vc.createVc(vc); + + await expect(promise).to.be.rejectedWith('No Subject ID provided'); + }); + }); + + describe('When storing a VC onchain', async () => { + it('allows me to store a valid VC on-chain (and registering an ID implicitly)', async () => { + const promise = runtime.vc.storeVc(minimalValidVcData); + await expect(promise).to.not.be.rejected; + const storedVc = await promise; + await expect(didJWT.verifyJWT(storedVc.proof.jws, { resolver: evanResolver })) + .to.not.be.rejected; + const verifiedSignature = await didJWT.verifyJWT( + storedVc.proof.jws, + { resolver: evanResolver }, + ); + + // fails if signed VC and proof-carrying VC not matching + await compareDocuments(storedVc, verifiedSignature.payload.vc); + }); + + it('adds a credentialStatus property to the VC document when storing on-chain', async () => { + const promise = runtime.vc.storeVc(minimalValidVcData); + const endpointUrl = runtime.vc.credentialStatusEndpoint; + await expect(promise).to.not.be.rejected; + + const doc = await promise; + expect(doc.credentialStatus.id).to.eq(`${endpointUrl}${doc.id}`); + }); + + it('allows me to store a valid VC on-chain under my registered ID', async () => { + const myRegisteredId = await runtime.vc.createId(); + const myDoc: VcDocumentTemplate = { ...minimalValidVcData }; + myDoc.id = myRegisteredId; + const promise = runtime.vc.storeVc(myDoc); + + await expect(promise).to.not.be.rejected; + expect((await promise).id).to.eq(myRegisteredId); + }); + + it('does not allow me to store a valid VC on-chain under an invalid ID', async () => { + const invalidId = 'invalidId'; + const myDoc: VcDocumentTemplate = { + ...minimalValidVcData, + id: invalidId, + }; + const promise = runtime.vc.storeVc(myDoc); + + await expect(promise) + .to.be.rejectedWith(`Given VC ID ("${invalidId}") is no valid evan VC ID`); + }); + + it('(API level) does not allow me to store a VC under an ID I do not own', async () => { + // Have another identity create a VC that we then want to store + const otherRuntime = await TestUtils.getRuntime(accounts[1], null, { useIdentity: true }); + const someoneElsesId = await otherRuntime.vc.createId(); + + const vcData = { + ...minimalValidVcData, + id: someoneElsesId, + }; + + const promise = runtime.vc.storeVc(vcData); + await expect(promise).to.be.rejectedWith( + `Active identity is not the owner of the given VC ID ${someoneElsesId}`, + ); + }); + + it('(contract level) does not allow me to store a VC under an ID I do not own', async () => { + // Have another identity create a VC that we then want to store + const otherRuntime = await TestUtils.getRuntime(accounts[1], null, { useIdentity: true }); + const someoneElsesId = (await otherRuntime.vc.createId()).replace('vc:evan:testcore:', ''); + + // Try to write a fake dfs hash under someone else's registered ID + const promise = runtime.executor.executeContractTransaction( + await (runtime.vc as any).getRegistryContract(), + 'setVc', + { from: runtime.activeIdentity }, + someoneElsesId, + '0x2a838a6961be98f6a182f375bb9158848ee9760ca97a379939ccdf03fc442a23', // fake address + ); + + // Expect that the transaction is rejected since we do not own the address + await expect(promise).to.be.rejectedWith('could not estimate gas usage'); + }); + + it('allows me to get an existing VC using the full VC ID URI', async () => { + const storedVcDoc = await runtime.vc.storeVc(minimalValidVcData); + const promise = runtime.vc.getVc(storedVcDoc.id); + + await expect(promise).to.not.be.rejected; + expect((await promise).id).to.eq(storedVcDoc.id); + }); + + it('allows me to get an existing VC using only the VC ID (discarding vc:evan prefix)', async () => { + const storedVcDoc = await runtime.vc.storeVc(minimalValidVcData); + const promise = runtime.vc.getVc(storedVcDoc.id.replace('vc:evan:testcore:', '')); + + await expect(promise).to.not.be.rejected; + expect((await promise).id).to.eq(storedVcDoc.id); + }); + + it('does not allow me to get a valid but non-existing VC', async () => { + const nonExistingVcId = '0x2a838a6961be98f6a182f375bb9158848ee9760ca97a379939ccdf03fc442a23'; + const fetchedVcDoc = runtime.vc.getVc(nonExistingVcId); + + expect(fetchedVcDoc).to.be.rejectedWith(`VC for address ${nonExistingVcId} does not exist`); + }); + + it('does not allow me to get an existing VC in the wrong environment', async () => { + const storedVcDoc = await runtime.vc.storeVc(minimalValidVcData); + const fetchedVcDoc = runtime.vc.getVc(storedVcDoc.id.replace('testcore:', 'core:')); + + await expect(fetchedVcDoc) + .to.be.rejectedWith('Given VC ID environment "core" does not match current "testcore"'); + }); + }); + + describe('When revoking a VC', async () => { + it('allows me to store online VC and revoke it using the bcc', async () => { + const storedVcDoc = await runtime.vc.storeVc(minimalValidVcData); + const issuerId = storedVcDoc.issuer.id.replace('did:evan:testcore:', ''); + expect(issuerId).to.be.eq(issuerIdentityId); + + const vcId = storedVcDoc.id.replace('vc:evan:testcore:', ''); + const vcRevokeStatus = await runtime.executor.executeContractCall( + await (runtime.vc as any).getRegistryContract(), + 'vcRevoke', + vcId, + ); + expect(vcRevokeStatus).to.be.false; + + const revokeProcessed = runtime.executor.executeContractTransaction( + await (runtime.vc as any).getRegistryContract(), + 'revokeVC', + { from: runtime.activeIdentity }, + vcId, + ); + await expect(revokeProcessed).to.be.not.rejected; + + const vcRevokeStatusNew = await runtime.executor.executeContractCall( + await (runtime.vc as any).getRegistryContract(), + 'vcRevoke', + vcId, + ); + expect(vcRevokeStatusNew).to.be.not.false; + }); + + it('allows me to store online VC and revoke it using the vc api', async () => { + const storedVcDoc = await runtime.vc.storeVc(minimalValidVcData); + const issuerId = storedVcDoc.issuer.id.replace('did:evan:testcore:', ''); + expect(issuerId).to.be.eq(issuerIdentityId); + + const vcId = storedVcDoc.id; + + const vcRevokeStatus = await runtime.vc.getRevokeVcStatus(vcId); + expect(vcRevokeStatus).to.be.false; + + const revokeProcessed = runtime.vc.revokeVc(vcId); + await expect(revokeProcessed).to.be.not.rejected; + + const vcRevokeStatusNew = await runtime.vc.getRevokeVcStatus(vcId); + expect(vcRevokeStatusNew).to.be.not.false; + }); + + it('does not allow me to revoke a non existing VC using the vc api', async () => { + const nonExistingVcId = '0x2a838a6961be98f6a182f375bb9158848ee9760ca97a379939ccdf03fc442a23'; + + const vcRevokeStatus = await runtime.vc.getRevokeVcStatus(nonExistingVcId); + expect(vcRevokeStatus).to.be.false; + + const revokeProcessed = runtime.vc.revokeVc(nonExistingVcId); + await expect(revokeProcessed).to.be.rejected; + + const vcRevokeStatusNew = await runtime.vc.getRevokeVcStatus(nonExistingVcId); + expect(vcRevokeStatusNew).to.be.false; + }); + + it('allows me to get revoke status of an existing VC using the vc api', async () => { + const storedVcDoc = await runtime.vc.storeVc(minimalValidVcData); + const issuerId = storedVcDoc.issuer.id.replace('did:evan:testcore:', ''); + expect(issuerId).to.be.eq(issuerIdentityId); + const vcId = storedVcDoc.id; + + const vcRevokeStatus = await runtime.vc.getRevokeVcStatus(vcId); + expect(vcRevokeStatus).to.be.false; + }); + + it('does not allow me to revoke VC using non issuer account via bcc', async () => { + const storedVcDoc = await runtime.vc.storeVc(minimalValidVcData); + const issuerId = storedVcDoc.issuer.id.replace('did:evan:testcore:', ''); + expect(issuerId).to.be.eq(issuerIdentityId); + const vcId = storedVcDoc.id.replace('vc:evan:testcore:', ''); + + const otherRuntime = await TestUtils.getRuntime(accounts[1], null, { useIdentity: true }); + + const vcRevokeStatus = await runtime.executor.executeContractCall( + await (runtime.vc as any).getRegistryContract(), + 'vcRevoke', + vcId, + ); + expect(vcRevokeStatus).to.be.false; + + const revokeProcessed = otherRuntime.executor.executeContractTransaction( + await (otherRuntime.vc as any).getRegistryContract(), + 'revokeVC', + { from: otherRuntime.activeIdentity }, + vcId, + ); + await expect(revokeProcessed).to.be.rejected; + + const vcRevokeStatusNew = await runtime.executor.executeContractCall( + await (runtime.vc as any).getRegistryContract(), + 'vcRevoke', + vcId, + ); + expect(vcRevokeStatusNew).to.be.false; + }); + + it('allows me to store online VC but not revoke it using non issuer account via vc api', async () => { + const storedVcDoc = await runtime.vc.storeVc(minimalValidVcData); + const issuerId = storedVcDoc.issuer.id.replace('did:evan:testcore:', ''); + expect(issuerId).to.be.eq(issuerIdentityId); + + const vcId = storedVcDoc.id; + const otherRuntime = await TestUtils.getRuntime(accounts[1], null, { useIdentity: true }); + + const vcRevokeStatus = await runtime.vc.getRevokeVcStatus(vcId); + expect(vcRevokeStatus).to.be.false; + + const revokeProcessed = otherRuntime.vc.revokeVc(vcId); + await expect(revokeProcessed).to.be.rejected; + + const vcRevokeStatusNew = await runtime.vc.getRevokeVcStatus(vcId); + expect(vcRevokeStatusNew).to.be.false; + }); + }); +}); diff --git a/src/vc/vc.ts b/src/vc/vc.ts new file mode 100644 index 00000000..c415417b --- /dev/null +++ b/src/vc/vc.ts @@ -0,0 +1,602 @@ +/* + Copyright (C) 2018-present evan GmbH. + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License, version 3, + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see http://www.gnu.org/licenses/ or + write to the Free Software Foundation, Inc., 51 Franklin Street, + Fifth Floor, Boston, MA, 02110-1301 USA, or download the license from + the following URL: https://evan.network/license/ +*/ + +import * as didJWT from 'did-jwt'; +import { cloneDeep } from 'lodash'; + +import { + nullBytes32, + getEnvironment, +} from '../common/utils'; + +import { + AccountStore, + ContractLoader, + DfsInterface, + Did, + Executor, + Logger, + LoggerOptions, + NameResolver, + SignerIdentity, + Verifications, +} from '../index'; + + +/** + * custom configuration for VC resolver + */ +export interface VcConfig { + credentialStatusEndpoint: string; +} + +/** + * A VC's credential status property + */ +export interface VcCredentialStatus { + id: string; + type: string; +} + +/** + * Information about a VC's subject + */ +export interface VcCredentialSubject { + id: string; + data?: VcCredentialSubjectPayload[]; + description?: string; + uri?: string; +} + +/** + * (Optional) Payload for a VC credential subject + */ +export interface VcCredentialSubjectPayload { + name: string; + value: string; +} + +/** + * A valid VC document + */ +export interface VcDocument { + '@context': string[]; + id: string; + type: string[]; + issuer: VcIssuer; + validFrom: string; + validUntil?: string; + credentialSubject: VcCredentialSubject; + credentialStatus?: VcCredentialStatus; + proof?: VcProof; +} + +/** + * Template for a VC that will be converted into a valid VC by the resolver + */ +export interface VcDocumentTemplate { + '@context'?: string[]; + id?: string; + type?: string[]; + issuer: VcIssuer; + validFrom: string; + validUntil?: string; + credentialSubject: VcCredentialSubject; + credentialStatus?: VcCredentialStatus; +} +/** + * The parts an VC ID in evan is made of + */ +export interface VcIdSections { + environment: string; + internalId: string; +} + +/** + * Issuer of a VC + */ +export interface VcIssuer { + id: string; + name?: string; +} + +/** + * Options for VC resolver + */ +export interface VcOptions extends LoggerOptions { + accountStore: AccountStore; + activeAccount: string; + contractLoader: ContractLoader; + dfs: DfsInterface; + did: Did; + executor: Executor; + nameResolver: NameResolver; + signerIdentity: SignerIdentity; + verifications: Verifications; + web3: any; +} + +/** + * The proof for a VC, containing the JWS and metadata + */ +export interface VcProof { + type: string; + created: string; + proofPurpose: string; + verificationMethod: string; + jws: string; +} + +/** + * Holds a list of supported proof types for VC (JWS) proofs + */ +export const enum VcProofType { + EcdsaPublicKeySecp256k1 = 'EcdsaPublicKeySecp256k1', +} + +const JWTProofMapping = {}; +JWTProofMapping[(VcProofType.EcdsaPublicKeySecp256k1)] = 'ES256K-R'; + +const vcRegEx = /^vc:evan:(?:(testcore|core):)?(0x(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64}))$/; + +const w3cMandatoryContext = 'https://www.w3.org/2018/credentials/v1'; + +/** + * Module for storing VCs in and retrieving VCs from the VC registry + * + * @class Vc + */ +export class Vc extends Logger { + public credentialStatusEndpoint: string; + + private cache: any = {}; + + private config: VcConfig; + + private options: VcOptions; + + /** + * Creates a new `Vc` instance + * + * @param {VcOptions} options options for `Vc` + * @param {Did} did Instance of `Did` used for resolving DIDs + */ + public constructor(options: VcOptions, config: VcConfig) { + super(options as LoggerOptions); + this.options = options; + this.credentialStatusEndpoint = config.credentialStatusEndpoint; + } + + /** + * Associates the active identity with a new ID in the registry to store a VC at. + * + * @returns {Promise} The reserved ID. + */ + public async createId(): Promise { + const id = await this.options.executor.executeContractTransaction( + await this.getRegistryContract(), + 'createId', { + from: this.options.signerIdentity.activeIdentity, + event: { target: 'VcRegistry', eventName: 'VcIdRegistered' }, + getEventResult: (event, args) => args.vcId, + }, + ); + + return this.convertInternalVcIdToUri(id); + } + + /** + * Creates a new VC document from a template. + * + * @param {VcDocumentTemplate} vcData Template for the VC document containing the relevant + * data. + * @returns {Promise { + if (!vcData.id) { + throw new Error('VC misses id'); + } + + const types = vcData.type ? vcData.type : ['VerifiableCredential']; + + const context = vcData['@context'] ? vcData['@context'] : [w3cMandatoryContext]; + if (!context.includes(w3cMandatoryContext)) { + context.push(w3cMandatoryContext); + } + + const vcDocument: VcDocument = { + '@context': context, + type: types, + ...(vcData as VcDocument), + }; + + vcDocument.proof = await this.createProofForVc(vcDocument); + + await this.validateVcDocument(vcDocument); + + return vcDocument; + } + + /** + * get the Revoke status of a given VC document + * + * @param {string} vcId The registry ID the VC document is associated with. + * @return {revokationStatus} A boolean value. False = not revoked, True = revoked + */ + public async getRevokeVcStatus(vcId: string): Promise { + const environment = await this.getEnvironment(); + const vcIdHash = vcId.replace(`vc:evan:${environment}:`, ''); + const revokationStatus = await this.options.executor.executeContractCall( + await this.getRegistryContract(), + 'vcRevoke', + vcIdHash, + ); + + return revokationStatus; + } + + /** + * Returns a VC document for a given ID. + * + * @param {string} vcId The registry ID the VC document is associated with. + * @returns {Promise { + // Check whether the full URI (vc:evan:[vcId]) or just the internal ID was given + let identityAddress = vcId; + if (!identityAddress.startsWith('0x')) { + const groups = vcRegEx.exec(vcId); + if (!groups) { + throw new Error(`Given VC ID ("${vcId}") is no valid evan VC ID`); + } + const [, vcEnvironment = 'core', address] = groups; + identityAddress = address; + const environment = await this.getEnvironment(); + if ((environment === 'testcore' && vcEnvironment !== 'testcore') + || (environment === 'core' && vcEnvironment !== 'core')) { + throw new Error(`Given VC ID environment "${vcEnvironment}" does not match current "${environment}"`); + } + } + + const vcDfsHash = await this.options.executor.executeContractCall( + await this.getRegistryContract(), + 'vcStore', + identityAddress, + ); + + if (vcDfsHash === nullBytes32) { + throw Error(`VC for address ${vcId} does not exist`); + } + const document = JSON.parse(await this.options.dfs.get(vcDfsHash) as any) as VcDocument; + await this.validateProof(document); + return document; + } + + /** + * Revokes a given VC document + * + * @param {string} vcId The registry ID the VC document is associated with. + * @return {Promise} resolved when done + */ + public async revokeVc(vcId: string): Promise { + const environment = await this.getEnvironment(); + const vcIdHash = vcId.replace(`vc:evan:${environment}:`, ''); + await this.validateVcIdOwnership(vcIdHash); + await this.options.executor.executeContractTransaction( + await this.getRegistryContract(), + 'revokeVC', + { from: this.options.signerIdentity.activeIdentity }, + vcIdHash, + ); + } + + /** + * Stores the given VC document in the registry under the provided ID. + * The ID has to be a valid and registered VC ID. + * Creates a proof if none is given or validates it if one is given. + * + * @param {VcDocumentTemplate} vcData Template for the VC document containing the relevant + * data. + * @returns {Promise { + const dataTemplate: VcDocumentTemplate = cloneDeep(vcData); + let internalId; + + if (!dataTemplate.id) { + dataTemplate.id = await this.createId(); + internalId = (await this.validateVcIdAndGetSections(dataTemplate.id)).internalId; + } else { + // We prefix the ID specified in the document with + // the evan identifier (vc:evan:[core|testcore]:) + // However, we only need the actual ID to address the registry + const sections = await this.validateVcIdAndGetSections(dataTemplate.id); + internalId = sections.internalId; + // Is the given VC ID valid and the active identity the owner of the VC ID? + await this.validateVcIdOwnership(internalId); + } + + dataTemplate.credentialStatus = { + id: `${this.credentialStatusEndpoint}${dataTemplate.id}`, + type: 'evan:evanCredential', + }; + + const documentToStore = await this.createVc(dataTemplate); + + const vcDfsAddress = await this.options.dfs.add('vc', + Buffer.from(JSON.stringify(documentToStore), 'utf-8')); + await this.options.executor.executeContractTransaction( + await this.getRegistryContract(), + 'setVc', + { from: this.options.signerIdentity.activeIdentity }, + internalId, + vcDfsAddress, + ); + + return documentToStore; + } + + /** + * Converts an interal VC ID (0x...) to a URI (vc:evan:...) + * + * @param internalVcId Internal 32bytes ID + * @returns The VC's URI + */ + private async convertInternalVcIdToUri(internalVcId: string): Promise { + const environment = await this.getEnvironment(); + + return `vc:evan:${environment}:${internalVcId}`; + } + + /** + * Create a JWT over a VC document + * + * @param {VcDocument} vc The VC document + * @param {VcProofType} proofType The type of algorithm used for generating the JWT + */ + private async createJwtForVc(vc: VcDocument, proofType: VcProofType): Promise { + const signer = didJWT.SimpleSigner( + await this.options.accountStore.getPrivateKey(this.options.activeAccount), + ); + let jwt = ''; + await didJWT.createJWT( + { + vc, + exp: vc.validUntil, + }, { + alg: JWTProofMapping[proofType], + issuer: vc.issuer.id, + signer, + }, + ).then((response) => { jwt = response; }); + + return jwt; + } + + /** + * Creates a new `VcProof` object for a given VC document, including generating a JWT token over + * the whole document. + * + * @param {VcDocument} vc The VC document to create the proof for. + * @param {VcProofType} proofType Specify if you want a proof type different from the + * default one. + * @returns {VcProof} A proof object containing a JWT. + * @throws If the VC issuer identity and the signer identity differ from each other + */ + private async createProofForVc(vc: VcDocument, + proofType: VcProofType = VcProofType.EcdsaPublicKeySecp256k1): Promise { + let issuerIdentity; + try { + issuerIdentity = await this.options.did.convertDidToIdentity(vc.issuer.id); + } catch (e) { + throw Error(`Invalid issuer DID: ${vc.issuer.id}`); + } + const accountIdentity = await this.options.verifications.getIdentityForAccount( + this.options.activeAccount, + true, + ); + + if (accountIdentity !== issuerIdentity) { + throw Error('You are not authorized to issue this VC'); + } + + const jwt = await this.createJwtForVc(vc, proofType); + + const verMethod = await this.getPublicKeyUriFromDid(vc.issuer.id); + + const proof: VcProof = { + type: `${proofType}`, + created: new Date(Date.now()).toISOString(), + proofPurpose: 'assertionMethod', + verificationMethod: verMethod, + jws: jwt, + }; + + return proof; + } + + /** + * Get current environment ('testcore:' || 'core'). Result is cached. + * + * @returns {Promise} current environment + */ + private async getEnvironment(): Promise { + if (!this.cache.environment) { + this.cache.environment = await getEnvironment(this.options.web3); + } + return this.cache.environment; + } + + /** + * Retrieves the ID of the public key of an VC's issuer's DID document that matches the active + * identity's public key. + * + * @param {string} issuerDid DID of the VC issuer. + * @throws If there is no authentication material given in the DID or no key matching + * the active identity is found. + */ + private async getPublicKeyUriFromDid(issuerDid: string): Promise { + const signaturePublicKey = await this.options.signerIdentity.getPublicKey( + this.options.signerIdentity.underlyingAccount, + ); + const doc = await this.options.did.getDidDocument(issuerDid); + + if (!(doc.authentication || doc.publicKey || doc.publicKey.length === 0)) { + throw Error(`Document for ${issuerDid}` + + 'does not provide authentication material. Cannot sign VC.'); + } + + const key = doc.publicKey.filter((entry) => entry.publicKeyHex === signaturePublicKey)[0]; + + if (!key) { + throw Error('The signature key for the active account is not associated to its DID document. Cannot sign VC.'); + } + + return key.id; + } + + /** + * Get web3 contract instance for VC registry contract via ENS. Result is cached. + * + * @returns {Promise} VC registry contract + */ + private async getRegistryContract(): Promise { + if (!this.cache.vcRegistryContract) { + const vcRegistryDomain = this.options.nameResolver.getDomainName( + this.options.nameResolver.config.domains.vcRegistry, + ); + const vcRegistryAddress = await this.options.nameResolver.getAddress(vcRegistryDomain); + + this.cache.vcRegistryContract = this.options.contractLoader.loadContract( + 'VcRegistry', vcRegistryAddress, + ); + } + + return this.cache.vcRegistryContract; + } + + /** + * Validates the JWS of a VC Document proof + * + * @param {VcDocument} document The VC Document + * @returns {Promise} Resolves when done + */ + private async validateProof(document: VcDocument): Promise { + // Mock the did-resolver package that did-jwt usually requires + const didResolver = this.options.did; + const resolver = { + async resolve() { + const doc = await didResolver.getDidDocument(document.issuer.id); + return doc as any; + }, + }; + + // fails if invalid signature + const verifiedSignature = await didJWT.verifyJWT(document.proof.jws, { resolver }); + + // fails if signed payload and the VC differ + const payload = { + ...verifiedSignature.payload.vc, + }; + delete payload.proof; + const prooflessDocument = { + ...document, + }; + delete prooflessDocument.proof; + + const proofPayloadHash = await this.options.nameResolver.soliditySha3(JSON.stringify(payload)); + const documentHash = await this.options.nameResolver.soliditySha3( + JSON.stringify(prooflessDocument), + ); + if (proofPayloadHash !== documentHash) { + throw Error('Invalid proof. Signed payload does not match given document.'); + } + } + + /** + * Checks various criteria a VC document has to meet + * + * @param {VcDocument} document The VC document to check. + * @returns {Promise} If the checks are succesfull. + * @throws If any of the criteria is not met. + */ + private async validateVcDocument(document: VcDocument): Promise { + // Subject + if (!document.credentialSubject.id || document.credentialSubject.id === '') { + throw new Error('No Subject ID provided'); + } + await this.options.did.validateDid(document.credentialSubject.id); + + // Issuer + if (!document.issuer.id || document.issuer.id === '') { + throw new Error('No Issuer ID provided'); + } + await this.options.did.validateDid(document.issuer.id); + + // Proof + if (!document.proof || !document.proof.jws || document.proof.jws === '') { + throw new Error('VC misses proof'); + } else if (!document.proof.type) { + throw new Error('VC proof misses type'); + } + await this.validateProof(document); + } + + /** + * Validates whether a given ID is a valid evan VC ID and returns + * its sections (environment and internal ID) + * + * @param vcId VC ID + * @returns {VcIdSections} Sections of the ID + */ + private async validateVcIdAndGetSections(vcId: string): Promise { + const groups = vcRegEx.exec(vcId); + if (!groups) { + throw new Error(`Given VC ID ("${vcId}") is no valid evan VC ID`); + } + const [, vcEnvironment = 'core', internalId] = groups; + const environment = await this.getEnvironment(); + if ((environment === 'testcore' && vcEnvironment !== 'testcore') + || (environment === 'core' && vcEnvironment !== 'core')) { + throw new Error(`VCs environment "${environment} does not match ${vcEnvironment}`); + } + + return { environment: vcEnvironment, internalId }; + } + + /** + * Checks if the given VC ID is associated to the active identity at the VC registry + * + * @param vcId VC ID registered at the VC registry + * @returns {Promise} Resolves when successful + * @throws {Error} If the ID is not valid or the identity is not the ID owner + */ + private async validateVcIdOwnership(vcId: string): Promise { + const ownerAddress = await this.options.executor.executeContractCall( + await this.getRegistryContract(), + 'vcOwner', + vcId, + ); + + if (this.options.signerIdentity.activeIdentity !== ownerAddress) { + throw Error(`Active identity is not the owner of the given VC ID ${await this.convertInternalVcIdToUri(vcId)}`); + } + } +} diff --git a/src/verifications/verifications.spec.ts b/src/verifications/verifications.spec.ts index 32b0c313..4db6ac3c 100644 --- a/src/verifications/verifications.spec.ts +++ b/src/verifications/verifications.spec.ts @@ -18,7 +18,7 @@ */ import 'mocha'; -import chaiAsPromised = require('chai-as-promised'); +import * as chaiAsPromised from 'chai-as-promised'; import { expect, use } from 'chai'; import { ContractLoader, Executor } from '@evan.network/dbcp'; @@ -38,42 +38,18 @@ import { } from './verifications'; function timeout(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } use(chaiAsPromised); -describe('Verifications handler', function() { +describe('Verifications handler', function test() { this.timeout(600000); function getRandomTopic(prefix: string) { - return `${ prefix }/${ Date.now().toString() + Math.random().toString().slice(2, 20) }`; + return `${prefix}/${Date.now().toString() + Math.random().toString().slice(2, 20)}`; } - const singedByAccounts0 = async (verification) => { - if (verification.details.issuer === accounts[0]) { - return VerificationsStatusV2.Green; - } - return VerificationsStatusV2.Red; - }; - - const validationOptions: VerificationsValidationOptions = { - disableSubVerifications: VerificationsStatusV2.Red, - expired: VerificationsStatusV2.Red, - invalid: VerificationsStatusV2.Red, - issued: VerificationsStatusV2.Green, - missing: VerificationsStatusV2.Red, - noIdentity: VerificationsStatusV2.Red, - notEnsRootOwner: VerificationsStatusV2.Red, - parentMissing: VerificationsStatusV2.Yellow, - parentUntrusted: singedByAccounts0, - rejected: VerificationsStatusV2.Red, - selfIssued: VerificationsStatusV2.Red, - }; - const queryOptions: VerificationsQueryOptions = { - validationOptions, - }; - let baseContract: BaseContract; let verifications: Verifications; let contractLoader: ContractLoader; @@ -99,22 +75,26 @@ describe('Verifications handler', function() { const deploy = async (contractAndPath) => { const contractName = /^[^:]*:(.*)$/g.exec(contractAndPath)[1]; const replace = (target, name, address) => { - contractLoader.contracts[target].bytecode = - contractLoader.contracts[target].bytecode.replace( + contractLoader.contracts[target].bytecode = contractLoader.contracts[target].bytecode + .replace( new RegExp(contractLoader.contracts[name] - .deployedAt.slice(2), 'g'), address.slice(2)); + .deployedAt.slice(2), 'g'), address.slice(2), + ); }; const updateBytecode = (librayName, libraryAddress) => { + // eslint-disable-next-line Object.keys(contractLoader.contracts).map((contract) => { const before = contractLoader.contracts[contract].bytecode; replace(contract, librayName, libraryAddress); if (before !== contractLoader.contracts[contract].bytecode) { - console.log(`updated: ${contract}`) + // eslint-disable-next-line + console.log(`updated: ${contract}`); } }); }; libs[contractAndPath] = (await executor.createContract( - contractName, [], { from: accounts[0], gas: 3000000 })).options.address; + contractName, [], { from: accounts[0], gas: 3000000 }, + )).options.address; updateBytecode(contractName, libs[contractAndPath]); }; @@ -122,7 +102,8 @@ describe('Verifications handler', function() { await deploy('verifications/VerificationHolderLibrary.sol:VerificationHolderLibrary'); await deploy('verifications/VerificationsRegistryLibrary.sol:VerificationsRegistryLibrary'); - for (let key of Object.keys(libs)) { + for (const key of Object.keys(libs)) { + // eslint-disable-next-line console.log(`${/[^:]:(.*)/g.exec(key)[1]}: ${libs[key].slice(2)}`); } }); @@ -148,7 +129,8 @@ describe('Verifications handler', function() { const oldLength = (await verifications.getVerifications(accounts[1], '/company')).length; await timeout(1000); const verificationId = await verifications.setVerification( - accounts[0], accounts[1], '/company'); + accounts[0], accounts[1], '/company', + ); await timeout(1000); expect(verificationId).to.be.ok; const verificationsForAccount = await verifications.getVerifications(accounts[1], '/company'); @@ -160,14 +142,14 @@ describe('Verifications handler', function() { it('can add a verification with data', async () => { const oldLength = (await verifications.getVerifications(accounts[1], '/company')).length; await timeout(2000); - await verifications.setVerification(accounts[0], accounts[1], '/company', 0, {foo: 'bar'}); + await verifications.setVerification(accounts[0], accounts[1], '/company', 0, { foo: 'bar' }); await timeout(2000); const verificationsForAccount = await verifications.getVerifications(accounts[1], '/company'); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); }); it('can add a verification with encrypted data', async () => { - const unencrypted = {foo: 'bar'}; + const unencrypted = { foo: 'bar' }; const cryptoInfo = await encryptionWrapper.getCryptoInfo('test', EncryptionWrapperKeyType.Custom); const key = await encryptionWrapper.generateKey(cryptoInfo); const encrypted = await encryptionWrapper.encrypt(unencrypted, cryptoInfo, { key }); @@ -194,7 +176,8 @@ describe('Verifications handler', function() { it('can add a verification with a special verification uri', async () => { const oldLength = (await verifications.getVerifications( - accounts[1], '/company')).length; + accounts[1], '/company', + )).length; await verifications.setVerification( accounts[0], accounts[1], @@ -204,10 +187,11 @@ describe('Verifications handler', function() { null, false, false, - 'http://google.de' + 'http://google.de', ); const verificationsForAccount = await verifications.getVerifications( - accounts[1], '/company'); + accounts[1], '/company', + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); expect(verificationsForAccount[oldLength]) .to.have.property('uri', 'http://google.de'); @@ -228,7 +212,8 @@ describe('Verifications handler', function() { it('can add subverification paths', async () => { const oldLength = (await verifications.getVerifications( - accounts[1], '/company/b-s-s/employee/swo3')).length; + accounts[1], '/company/b-s-s/employee/swo3', + )).length; await verifications.setVerification(accounts[0], accounts[0], '/company'); await verifications.setVerification(accounts[0], accounts[0], '/company/b-s-s'); await verifications.setVerification(accounts[0], accounts[0], '/company/b-s-s/employee'); @@ -237,26 +222,30 @@ describe('Verifications handler', function() { .getVerifications(accounts[1], '/company/b-s-s/employee/swo3'); expect(verificationsForAccount).to.have.lengthOf(1); expect(verificationsForAccount[oldLength]).to.have.property( - 'status', VerificationsStatus.Issued); + 'status', VerificationsStatus.Issued, + ); }); it('can confirm a subverification paths with the subject user', async () => { const oldLength = (await verifications.getVerifications( - accounts[1], '/company/b-s-s/employee/swo4')).length; + accounts[1], '/company/b-s-s/employee/swo4', + )).length; await verifications.setVerification(accounts[0], accounts[0], '/company'); await verifications.setVerification(accounts[0], accounts[0], '/company/b-s-s'); await verifications.setVerification(accounts[0], accounts[0], '/company/b-s-s/employee'); const verificationId = await verifications.setVerification( - accounts[0], accounts[1], '/company/b-s-s/employee/swo4'); + accounts[0], accounts[1], '/company/b-s-s/employee/swo4', + ); await verifications.confirmVerification(accounts[1], accounts[1], verificationId); const verificationsForAccount = await verifications.getVerifications( - accounts[1], '/company/b-s-s/employee/swo4'); + accounts[1], '/company/b-s-s/employee/swo4', + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); expect(verificationsForAccount[oldLength]) .to.have.property('status', VerificationsStatus.Confirmed); }); - it('can track the creation date', async() => { + it('can track the creation date', async () => { const before = Math.floor(Date.now() / 1000); await timeout(1000); await verifications.setVerification(accounts[0], accounts[1], '/company'); @@ -270,7 +259,7 @@ describe('Verifications handler', function() { expect(parseInt(verificationsForAccount[last].creationDate, 10)).to.be.lte(after); }); - it('can track the expiration date and the expired flag is set correctly', async() => { + it('can track the expiration date and the expired flag is set correctly', async () => { const before = Math.floor(Date.now() / 1000); await verifications.setVerification(accounts[0], accounts[1], '/company', before); const after = Math.floor(Date.now() / 1000); @@ -289,15 +278,17 @@ describe('Verifications handler', function() { await verifications.setVerification(accounts[0], accounts[0], '/company/b-s-s/employee'); await timeout(1000); const verificationId = await verifications.setVerification( - accounts[0], accounts[1], '/company/b-s-s/employee/swo6'); + accounts[0], accounts[1], '/company/b-s-s/employee/swo6', + ); await timeout(1000); await verifications.deleteVerification(accounts[1], accounts[1], verificationId); const verificationsForAccount = await verifications.getVerifications( - accounts[1], '/company/b-s-s/employee/swo6'); + accounts[1], '/company/b-s-s/employee/swo6', + ); expect(verificationsForAccount).to.have.lengthOf(0); }); - it('can track the creation block', async() => { + it('can track the creation block', async () => { const before = await web3.eth.getBlockNumber(); await verifications.setVerification(accounts[0], accounts[1], '/company'); const after = await web3.eth.getBlockNumber(); @@ -309,7 +300,7 @@ describe('Verifications handler', function() { expect(parseInt(verificationsForAccount[last].creationBlock, 10)).to.be.lte(after); }); - it('can add a description to a verification', async() => { + it('can add a description to a verification', async () => { const sampleVerificationsDomain = 'sample'; const sampleVerificationTopic = '/company'; const sampleDescription = { @@ -320,11 +311,14 @@ describe('Verifications handler', function() { dbcpVersion: 1, }; await verifications.setVerificationDescription( - accounts[0], sampleVerificationTopic, sampleVerificationsDomain, sampleDescription); + accounts[0], sampleVerificationTopic, sampleVerificationsDomain, sampleDescription, + ); await verifications.setVerification( - accounts[0], accounts[1], sampleVerificationTopic, 0, null, sampleVerificationsDomain); + accounts[0], accounts[1], sampleVerificationTopic, 0, null, sampleVerificationsDomain, + ); const verificationsForAccount = await verifications.getVerifications( - accounts[1], sampleVerificationTopic); + accounts[1], sampleVerificationTopic, + ); const last = verificationsForAccount.length - 1; expect(verificationsForAccount[last]).to.have.property('status', VerificationsStatus.Issued); expect(verificationsForAccount[last]).to.have.property('creationBlock'); @@ -333,15 +327,18 @@ describe('Verifications handler', function() { it('can reject a verification', async () => { const oldLength = (await verifications.getVerifications( - accounts[1], '/company/b-s-s/employee/swo4')).length; + accounts[1], '/company/b-s-s/employee/swo4', + )).length; await verifications.setVerification(accounts[0], accounts[0], '/company'); await verifications.setVerification(accounts[0], accounts[0], '/company/b-s-s'); await verifications.setVerification(accounts[0], accounts[0], '/company/b-s-s/employee'); const verificationId = await verifications.setVerification( - accounts[0], accounts[1], '/company/b-s-s/employee/swo4'); + accounts[0], accounts[1], '/company/b-s-s/employee/swo4', + ); await verifications.rejectVerification(accounts[1], accounts[1], verificationId); const verificationsForAccount = await verifications.getVerifications( - accounts[1], '/company/b-s-s/employee/swo4'); + accounts[1], '/company/b-s-s/employee/swo4', + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); expect(verificationsForAccount[oldLength]) .to.have.property('status', VerificationsStatus.Rejected); @@ -349,19 +346,23 @@ describe('Verifications handler', function() { it('can reject a verification with a reason', async () => { const oldLength = (await verifications.getVerifications( - accounts[1], '/company/b-s-s/employee/swo4')).length; + accounts[1], '/company/b-s-s/employee/swo4', + )).length; await verifications.setVerification(accounts[0], accounts[0], '/company'); await verifications.setVerification(accounts[0], accounts[0], '/company/b-s-s'); await verifications.setVerification(accounts[0], accounts[0], '/company/b-s-s/employee'); await timeout(1000); const verificationId = await verifications.setVerification( - accounts[0], accounts[1], '/company/b-s-s/employee/swo4'); + accounts[0], accounts[1], '/company/b-s-s/employee/swo4', + ); await timeout(1000); await verifications.rejectVerification( - accounts[1], accounts[1], verificationId, { reason: 'denied' }); + accounts[1], accounts[1], verificationId, { reason: 'denied' }, + ); await timeout(1000); const verificationsForAccount = await verifications.getVerifications( - accounts[1], '/company/b-s-s/employee/swo4'); + accounts[1], '/company/b-s-s/employee/swo4', + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); expect(verificationsForAccount[oldLength]) .to.have.property('status', VerificationsStatus.Rejected); @@ -371,16 +372,20 @@ describe('Verifications handler', function() { it('can reject a verification with a reason from the issuer side', async () => { const oldLength = (await verifications.getVerifications( - accounts[1], '/company/b-s-s/employee/swo4')).length; + accounts[1], '/company/b-s-s/employee/swo4', + )).length; await verifications.setVerification(accounts[0], accounts[0], '/company'); await verifications.setVerification(accounts[0], accounts[0], '/company/b-s-s'); await verifications.setVerification(accounts[0], accounts[0], '/company/b-s-s/employee'); const verificationId = await verifications.setVerification( - accounts[0], accounts[1], '/company/b-s-s/employee/swo4'); + accounts[0], accounts[1], '/company/b-s-s/employee/swo4', + ); await verifications.rejectVerification( - accounts[0], accounts[1], verificationId, { reason: 'denied' }); + accounts[0], accounts[1], verificationId, { reason: 'denied' }, + ); const verificationsForAccount = await verifications.getVerifications( - accounts[1], '/company/b-s-s/employee/swo4'); + accounts[1], '/company/b-s-s/employee/swo4', + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); expect(verificationsForAccount[oldLength]) .to.have.property('status', VerificationsStatus.Rejected); @@ -393,31 +398,34 @@ describe('Verifications handler', function() { await verifications.setVerification(accounts[0], accounts[0], '/company/b-s-s'); await verifications.setVerification(accounts[0], accounts[0], '/company/b-s-s/employee'); const verificationId = await verifications.setVerification( - accounts[0], accounts[1], '/company/b-s-s/employee/swo4'); + accounts[0], accounts[1], '/company/b-s-s/employee/swo4', + ); await verifications.rejectVerification(accounts[1], accounts[1], verificationId); const reacceptedP = verifications.confirmVerification( - accounts[1], accounts[1], verificationId); + accounts[1], accounts[1], verificationId, + ); await expect(reacceptedP).to.be.rejected; }); describe('when validating nested verifications', () => { it('verifications for a subject that has no identity should throw', async () => { const randomAccount = TestUtils.getRandomAddress(); - let topic = getRandomTopic('/evan'); + const topic = getRandomTopic('/evan'); // check missing state const promise = verifications.getComputedVerification(randomAccount, topic); await expect(promise).to.be.rejected; // V2 - //// check missing state + // // check missing state const promise2 = verifications.getNestedVerificationsV2(randomAccount, topic); await expect(promise2).to.be.rejected; }); it('non existing verifications include the warning "missing" and status should be -1', async () => { - let topic = getRandomTopic('/evan'), computed; + let computed; + const topic = getRandomTopic('/evan'); // check missing state computed = await verifications.getComputedVerification(accounts[0], topic); @@ -425,9 +433,15 @@ describe('Verifications handler', function() { await expect(computed.warnings).to.include('missing'); // V2 - //// check missing state - const localQueryOptions = { validationOptions: { [VerificationsStatusFlagsV2.missing]: VerificationsStatusV2.Yellow }}; - let v2 = await verifications.getNestedVerificationsV2(accounts[0], topic, false, localQueryOptions); + // // check missing state + const localQueryOptions = { + validationOptions: { + [VerificationsStatusFlagsV2.missing]: VerificationsStatusV2.Yellow, + }, + }; + let v2 = await verifications.getNestedVerificationsV2( + accounts[0], topic, false, localQueryOptions, + ); await expect(v2.status).to.be.eq(VerificationsStatusV2.Red); // check missing state is missing after set @@ -437,40 +451,44 @@ describe('Verifications handler', function() { await expect(computed.warnings).to.not.include('missing'); // V2 - //// check missing state is missing after set - v2 = await verifications.getNestedVerificationsV2(accounts[0], topic, false, localQueryOptions); + // // check missing state is missing after set + v2 = await verifications.getNestedVerificationsV2( + accounts[0], topic, false, localQueryOptions, + ); await expect(v2.status).not.to.be.eq(VerificationsStatusV2.Yellow); - await expect(v2.verifications[0].statusFlags).not.to.include(VerificationsStatusFlagsV2.missing); - } - ); + await expect(v2.verifications[0].statusFlags) + .not.to.include(VerificationsStatusFlagsV2.missing); + }); it('should be able to fetch a netsted parent path', async () => { - let parentTopic = getRandomTopic('/evan'); - let topic = getRandomTopic(parentTopic); + const parentTopic = getRandomTopic('/evan'); + const topic = getRandomTopic(parentTopic); // check issued case await verifications.setVerification(accounts[0], accounts[0], parentTopic); await verifications.setVerification(accounts[0], accounts[1], topic); - await new Promise(s => setTimeout(s, 1000)); + await new Promise((s) => setTimeout(s, 1000)); const localValidationOptions: VerificationsValidationOptions = { disableSubVerifications: VerificationsStatusV2.Red, - expired: VerificationsStatusV2.Red, - invalid: VerificationsStatusV2.Red, - issued: VerificationsStatusV2.Yellow, - missing: VerificationsStatusV2.Red, - noIdentity: VerificationsStatusV2.Red, - notEnsRootOwner: VerificationsStatusV2.Yellow, - parentMissing: VerificationsStatusV2.Yellow, - parentUntrusted: VerificationsStatusV2.Yellow, - rejected: VerificationsStatusV2.Red, - selfIssued: VerificationsStatusV2.Yellow, + expired: VerificationsStatusV2.Red, + invalid: VerificationsStatusV2.Red, + issued: VerificationsStatusV2.Yellow, + missing: VerificationsStatusV2.Red, + noIdentity: VerificationsStatusV2.Red, + notEnsRootOwner: VerificationsStatusV2.Yellow, + parentMissing: VerificationsStatusV2.Yellow, + parentUntrusted: VerificationsStatusV2.Yellow, + rejected: VerificationsStatusV2.Red, + selfIssued: VerificationsStatusV2.Yellow, }; const localQueryOptions: VerificationsQueryOptions = { validationOptions: localValidationOptions, }; - const nested = await verifications.getNestedVerificationsV2(accounts[1], topic, false, localQueryOptions); + const nested = await verifications.getNestedVerificationsV2( + accounts[1], topic, false, localQueryOptions, + ); expect(nested).to.haveOwnProperty('verifications'); expect(nested.verifications).to.have.length(1); expect(nested).to.haveOwnProperty('levelComputed'); @@ -480,7 +498,8 @@ describe('Verifications handler', function() { }); it('verifications with status 0 should have warning "issued"', async () => { - let topic = getRandomTopic('/evan'), computed; + let computed; + const topic = getRandomTopic('/evan'); // check issued case await verifications.setVerification(accounts[0], accounts[1], topic); @@ -490,13 +509,17 @@ describe('Verifications handler', function() { // V2 // check issued case - const localQueryOptions = { validationOptions: { - [VerificationsStatusFlagsV2.issued]: VerificationsStatusV2.Yellow, - [VerificationsStatusFlagsV2.notEnsRootOwner]: VerificationsStatusV2.Yellow, - [VerificationsStatusFlagsV2.parentUntrusted]: VerificationsStatusV2.Yellow, - [VerificationsStatusFlagsV2.parentMissing]: VerificationsStatusV2.Yellow, - }}; - let v2 = await verifications.getNestedVerificationsV2(accounts[1], topic, false, localQueryOptions); + const localQueryOptions = { + validationOptions: { + [VerificationsStatusFlagsV2.issued]: VerificationsStatusV2.Yellow, + [VerificationsStatusFlagsV2.notEnsRootOwner]: VerificationsStatusV2.Yellow, + [VerificationsStatusFlagsV2.parentUntrusted]: VerificationsStatusV2.Yellow, + [VerificationsStatusFlagsV2.parentMissing]: VerificationsStatusV2.Yellow, + }, + }; + let v2 = await verifications.getNestedVerificationsV2( + accounts[1], topic, false, localQueryOptions, + ); await expect(v2.status).to.eq(VerificationsStatusV2.Yellow); await expect(v2.verifications[0].statusFlags).to.include(VerificationsStatusFlagsV2.issued); @@ -508,9 +531,12 @@ describe('Verifications handler', function() { // V2 // test issued is missing after confirm - v2 = await verifications.getNestedVerificationsV2(accounts[1], topic, false, localQueryOptions); + v2 = await verifications.getNestedVerificationsV2( + accounts[1], topic, false, localQueryOptions, + ); await expect(v2.status).to.eq(VerificationsStatusV2.Yellow); - await expect(v2.verifications[0].statusFlags).to.not.include(VerificationsStatusFlagsV2.issued); + await expect(v2.verifications[0].statusFlags) + .to.not.include(VerificationsStatusFlagsV2.issued); }); it('expired verifications should have warning "expired"', async () => { @@ -527,20 +553,23 @@ describe('Verifications handler', function() { [VerificationsStatusFlagsV2.expired]: VerificationsStatusV2.Yellow, [VerificationsStatusFlagsV2.issued]: VerificationsStatusV2.Green, [VerificationsStatusFlagsV2.parentMissing]: VerificationsStatusV2.Green, - } + }, }; - const v2 = await verifications.getNestedVerificationsV2(accounts[1], topic, false, localQueryOptions); + const v2 = await verifications.getNestedVerificationsV2( + accounts[1], topic, false, localQueryOptions, + ); await expect(v2.status).to.eq(VerificationsStatusV2.Yellow); - await expect(v2.verifications[0].statusFlags).to.include(VerificationsStatusFlagsV2.expired); + await expect(v2.verifications[0].statusFlags) + .to.include(VerificationsStatusFlagsV2.expired); }); it('verifications that are created by the same user should have warning "selfIssued"', async () => { - let topic = getRandomTopic('/evan'), computed; + const topic = getRandomTopic('/evan'); // check issued case await verifications.setVerification(accounts[0], accounts[0], topic); - computed = await verifications.getComputedVerification(accounts[0], topic); + const computed = await verifications.getComputedVerification(accounts[0], topic); await expect(computed.status).to.be.eq(0); await expect(computed.warnings).to.include('selfIssued'); @@ -550,19 +579,21 @@ describe('Verifications handler', function() { [VerificationsStatusFlagsV2.selfIssued]: VerificationsStatusV2.Yellow, [VerificationsStatusFlagsV2.issued]: VerificationsStatusV2.Green, [VerificationsStatusFlagsV2.parentMissing]: VerificationsStatusV2.Green, - } + }, }; - const v2 = await verifications.getNestedVerificationsV2(accounts[0], topic, false, localQueryOptions); + const v2 = await verifications.getNestedVerificationsV2( + accounts[0], topic, false, localQueryOptions, + ); await expect(v2.status).to.eq(VerificationsStatusV2.Yellow); - await expect(v2.verifications[0].statusFlags).to.include(VerificationsStatusFlagsV2.selfIssued); - } - ); + await expect(v2.verifications[0].statusFlags) + .to.include(VerificationsStatusFlagsV2.selfIssued); + }); it('verifications with an missing parent should have the warning "parentMissing"', async () => { let computed; - let topicParent = getRandomTopic(''); - let topic = getRandomTopic(topicParent); + const topicParent = getRandomTopic(''); + const topic = getRandomTopic(topicParent); // check issued case await verifications.setVerification(accounts[0], accounts[1], topic); @@ -577,145 +608,170 @@ describe('Verifications handler', function() { // allow user[0] to create verifications for itself [VerificationsStatusFlagsV2.selfIssued]: VerificationsStatusV2.Green, [VerificationsStatusFlagsV2.parentUntrusted]: VerificationsStatusV2.Green, - } + }, }; - let v2 = await verifications.getNestedVerificationsV2(accounts[1], topic, false, localQueryOptions); + let v2 = await verifications.getNestedVerificationsV2( + accounts[1], topic, false, localQueryOptions, + ); await expect(v2.status).to.eq(VerificationsStatusV2.Yellow); - await expect(v2.verifications[0].statusFlags).to.include(VerificationsStatusFlagsV2.parentMissing); + await expect(v2.verifications[0].statusFlags) + .to.include(VerificationsStatusFlagsV2.parentMissing); await verifications.setVerification(accounts[0], accounts[0], topicParent); computed = await verifications.getComputedVerification(accounts[1], topic); await expect(computed.warnings).to.not.include('parentMissing'); // V2 - v2 = await verifications.getNestedVerificationsV2(accounts[1], topic, false, localQueryOptions); + v2 = await verifications.getNestedVerificationsV2( + accounts[1], topic, false, localQueryOptions, + ); await expect(v2.status).to.eq(VerificationsStatusV2.Green); - await expect(v2.verifications[0].statusFlags).not.to.include(VerificationsStatusFlagsV2.parentMissing); - } - ); + await expect(v2.verifications[0].statusFlags) + .not.to.include(VerificationsStatusFlagsV2.parentMissing); + }); it('verifications with an untrusted parent should have the warning "parentUntrusted"', async () => { - let computed; - let topicParent = getRandomTopic('/evan'); - let topic = getRandomTopic(topicParent); + const topicParent = getRandomTopic('/evan'); + const topic = getRandomTopic(topicParent); // check issued case await verifications.setVerification(accounts[0], accounts[1], topic); await verifications.setVerification(accounts[0], accounts[0], topicParent); - computed = await verifications.getComputedVerification(accounts[1], topic); + const computed = await verifications.getComputedVerification(accounts[1], topic); await expect(computed.warnings).to.include('parentUntrusted'); // V2 - const localQueryOptions = { validationOptions: { - [VerificationsStatusFlagsV2.issued]: VerificationsStatusV2.Yellow, - [VerificationsStatusFlagsV2.parentMissing]: VerificationsStatusV2.Yellow, - [VerificationsStatusFlagsV2.parentUntrusted]: VerificationsStatusV2.Yellow, - [VerificationsStatusFlagsV2.selfIssued]: VerificationsStatusV2.Yellow, - }}; - const v2 = await verifications.getNestedVerificationsV2(accounts[1], topic, false, localQueryOptions); + const localQueryOptions = { + validationOptions: { + [VerificationsStatusFlagsV2.issued]: VerificationsStatusV2.Yellow, + [VerificationsStatusFlagsV2.parentMissing]: VerificationsStatusV2.Yellow, + [VerificationsStatusFlagsV2.parentUntrusted]: VerificationsStatusV2.Yellow, + [VerificationsStatusFlagsV2.selfIssued]: VerificationsStatusV2.Yellow, + }, + }; + const v2 = await verifications.getNestedVerificationsV2( + accounts[1], topic, false, localQueryOptions, + ); await expect(v2.status).to.eq(VerificationsStatusV2.Yellow); - await expect(v2.verifications[0].statusFlags).to.include(VerificationsStatusFlagsV2.parentUntrusted); - } - ); + await expect(v2.verifications[0].statusFlags) + .to.include(VerificationsStatusFlagsV2.parentUntrusted); + }); it('verifications with the base "/evan" should be issued by the evan root account', async () => { - let computed; - let topic = '/evan'; + const topic = '/evan'; // check issued case await verifications.setVerification(accounts[0], accounts[1], topic); - computed = await verifications.getComputedVerification(accounts[1], topic); + const computed = await verifications.getComputedVerification(accounts[1], topic); await expect(computed.warnings).to.include('notEnsRootOwner'); // V2 - const localQueryOptions = { validationOptions: { [VerificationsStatusFlagsV2.notEnsRootOwner]: VerificationsStatusV2.Yellow }}; - const v2 = await verifications.getNestedVerificationsV2(accounts[1], topic, false, localQueryOptions); + const localQueryOptions = { + validationOptions: { + [VerificationsStatusFlagsV2.notEnsRootOwner]: VerificationsStatusV2.Yellow, + }, + }; + const v2 = await verifications.getNestedVerificationsV2( + accounts[1], topic, false, localQueryOptions, + ); await expect(v2.status).to.eq(VerificationsStatusV2.Yellow); - await expect(v2.verifications[0].statusFlags).to.include(VerificationsStatusFlagsV2.notEnsRootOwner); - } - ); + await expect(v2.verifications[0].statusFlags) + .to.include(VerificationsStatusFlagsV2.notEnsRootOwner); + }); it('verifications V2 can be marked as "red" using a customComputer', async () => { - let topicParent = getRandomTopic(''); - let topic = getRandomTopic(topicParent); + const topicParent = getRandomTopic(''); + const topic = getRandomTopic(topicParent); // issue verifications await verifications.setVerification(accounts[0], accounts[1], topic); await verifications.setVerification(accounts[0], accounts[0], topicParent); - // Check the following case: We want to check verifications, that can be issued by the same + // Check the following case: We want to check verifications, + // that can be issued by the same // user, but the full path must be issued by them same account let expectedIssuer = accounts[1]; const localQueryOptions = { validationOptions: { - [ VerificationsStatusFlagsV2.issued ]: VerificationsStatusV2.Green, - [ VerificationsStatusFlagsV2.parentUntrusted ]: VerificationsStatusV2.Green, - [ VerificationsStatusFlagsV2.selfIssued ]: VerificationsStatusV2.Green, + [VerificationsStatusFlagsV2.issued]: VerificationsStatusV2.Green, + [VerificationsStatusFlagsV2.parentUntrusted]: VerificationsStatusV2.Green, + [VerificationsStatusFlagsV2.selfIssued]: VerificationsStatusV2.Green, }, statusComputer: ( subVerification: VerificationsResultV2, subQueryOptions: VerificationsQueryOptions, - status: any + status: any, ) => { if (status === VerificationsStatusV2.Red) { return status; - } else { - // allow evan as root issuer - const correctIssuer = subVerification.verifications - .some(verification => verification.details.issuer === expectedIssuer); - - // if it's not the correct - return correctIssuer ? status : VerificationsStatusV2.Red; } - } + // allow evan as root issuer + const correctIssuer = subVerification.verifications + .some((verification) => verification.details.issuer === expectedIssuer); + + // if it's not the correct + return correctIssuer ? status : VerificationsStatusV2.Red; + }, }; // check using a wrong issuer - let v2 = await verifications.getNestedVerificationsV2(accounts[1], topic, false, localQueryOptions); + let v2 = await verifications.getNestedVerificationsV2( + accounts[1], topic, false, localQueryOptions, + ); await expect(v2.status).to.eq(VerificationsStatusV2.Red); // check with correct issuer - expectedIssuer = accounts[0]; - v2 = await verifications.getNestedVerificationsV2(accounts[1], topic, false, localQueryOptions); + [expectedIssuer] = accounts; + v2 = await verifications.getNestedVerificationsV2( + accounts[1], topic, false, localQueryOptions, + ); await expect(v2.status).to.eq(VerificationsStatusV2.Green); - } - ); + }); - it('sub verifications, where the parent verifications has the property has ' + - '"disableSubVerifications" should be not valid', + it('sub verifications, where the parent verifications has the property has ' + + '"disableSubVerifications" should be not valid', async () => { - let parentTopic = getRandomTopic(''); - let topic = getRandomTopic(parentTopic); + const parentTopic = getRandomTopic(''); + const topic = getRandomTopic(parentTopic); // check issued case await verifications.setVerification( - accounts[0], accounts[0], parentTopic, 0, null, null, true); + accounts[0], accounts[0], parentTopic, 0, null, null, true, + ); await verifications.setVerification(accounts[0], accounts[1], topic); // load parent verifications and computed from child const parentComputed = await verifications.getComputedVerification( - accounts[0], parentTopic); + accounts[0], parentTopic, + ); const computed = await verifications.getComputedVerification(accounts[1], topic); await expect(parentComputed.disableSubVerifications).to.be.eq(true); await expect(computed.warnings).to.include('disableSubVerifications'); // V2 - const localQueryOptions = { validationOptions: { - [VerificationsStatusFlagsV2.disableSubVerifications]: VerificationsStatusV2.Yellow, - [VerificationsStatusFlagsV2.issued]: VerificationsStatusV2.Yellow, - [VerificationsStatusFlagsV2.parentUntrusted]: VerificationsStatusV2.Yellow, - }}; - const parentV2 = await verifications.getNestedVerificationsV2(accounts[0], parentTopic, false, localQueryOptions); + const localQueryOptions = { + validationOptions: { + [VerificationsStatusFlagsV2.disableSubVerifications]: VerificationsStatusV2.Yellow, + [VerificationsStatusFlagsV2.issued]: VerificationsStatusV2.Yellow, + [VerificationsStatusFlagsV2.parentUntrusted]: VerificationsStatusV2.Yellow, + }, + }; + const parentV2 = await verifications.getNestedVerificationsV2( + accounts[0], parentTopic, false, localQueryOptions, + ); await expect(parentV2.verifications[0].raw.disableSubVerifications).to.be.eq(true); - const computedV2 = await verifications.getNestedVerificationsV2(accounts[1], topic, false, localQueryOptions); + const computedV2 = await verifications.getNestedVerificationsV2( + accounts[1], topic, false, localQueryOptions, + ); await expect(computedV2.status).to.eq(VerificationsStatusV2.Yellow); - await expect(computedV2.verifications[0].statusFlags).to.include(VerificationsStatusFlagsV2.disableSubVerifications); + await expect(computedV2.verifications[0].statusFlags) + .to.include(VerificationsStatusFlagsV2.disableSubVerifications); }); }); }); @@ -734,10 +790,12 @@ describe('Verifications handler', function() { it('can add a verification', async () => { const oldLength = (await verifications.getVerifications( - subject, '/company', isIdentity)).length; + subject, '/company', isIdentity, + )).length; await verifications.setVerification(accounts[0], subject, '/company', ...extraArgs); const verificationsForAccount = await verifications.getVerifications( - subject, '/company', isIdentity); + subject, '/company', isIdentity, + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); expect(verificationsForAccount[oldLength]) .to.have.property('status', VerificationsStatus.Issued); @@ -746,29 +804,33 @@ describe('Verifications handler', function() { it('can add a verification from an account that is not the owner of the contract', async () => { const oldLength = (await verifications.getVerifications( - subject, '/company', isIdentity)).length; + subject, '/company', isIdentity, + )).length; await verifications.setVerification(accounts[1], subject, '/company', ...extraArgs); const verificationsForAccount = await verifications.getVerifications( - subject, '/company', isIdentity); + subject, '/company', isIdentity, + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); expect(verificationsForAccount[oldLength]) .to.have.property('status', VerificationsStatus.Issued); - } - ); + }); it('can add a verification with data', async () => { const oldLength = (await verifications.getVerifications( - subject, '/company', isIdentity)).length; + subject, '/company', isIdentity, + )).length; await verifications.setVerification( - accounts[0], subject, '/company', 0, {foo: 'bar'}, ...extraArgs.slice(2)); + accounts[0], subject, '/company', 0, { foo: 'bar' }, ...extraArgs.slice(2), + ); await timeout(1000); const verificationsForAccount = await verifications.getVerifications( - subject, '/company', isIdentity); + subject, '/company', isIdentity, + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); }); it('can add a verification with encrypted data', async () => { - const unencrypted = {foo: 'bar'}; + const unencrypted = { foo: 'bar' }; const cryptoInfo = await encryptionWrapper.getCryptoInfo('test', EncryptionWrapperKeyType.Custom); const key = await encryptionWrapper.generateKey(cryptoInfo); const encrypted = await encryptionWrapper.encrypt(unencrypted, cryptoInfo, { key }); @@ -782,12 +844,15 @@ describe('Verifications handler', function() { it('can add a verification with specific expirationDate', async () => { const oldLength = (await verifications.getVerifications( - subject, '/company', isIdentity)).length; + subject, '/company', isIdentity, + )).length; const now = Math.floor(Date.now() / 1000); await verifications.setVerification( - accounts[0], subject, '/company', now, ...extraArgs.slice(1)); + accounts[0], subject, '/company', now, ...extraArgs.slice(1), + ); const verificationsForAccount = await verifications.getVerifications( - subject, '/company', isIdentity); + subject, '/company', isIdentity, + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); expect(verificationsForAccount[oldLength]) .to.have.property('expirationDate', now.toString()); @@ -796,7 +861,8 @@ describe('Verifications handler', function() { it('can add a verification with a special verification uri', async () => { const identityCheck = subject.length === 66; const oldLength = (await verifications.getVerifications( - subject, '/company', identityCheck)).length; + subject, '/company', identityCheck, + )).length; await verifications.setVerification( accounts[0], subject, @@ -806,10 +872,11 @@ describe('Verifications handler', function() { null, false, identityCheck, - 'http://google.de' + 'http://google.de', ); const verificationsForAccount = await verifications.getVerifications( - subject, '/company', identityCheck); + subject, '/company', identityCheck, + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); expect(verificationsForAccount[oldLength]) .to.have.property('uri', 'http://google.de'); @@ -817,31 +884,39 @@ describe('Verifications handler', function() { it('can add a verification and validate the integrity', async () => { const oldLength = (await verifications.getVerifications( - subject, '/company', isIdentity)).length; + subject, '/company', isIdentity, + )).length; await timeout(1000); await verifications.setVerification(accounts[0], subject, '/company', ...extraArgs); await timeout(1000); const verificationsForAccount = await verifications.getVerifications( - subject, '/company', isIdentity); + subject, '/company', isIdentity, + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); await verifications.validateVerification( - subject, verificationsForAccount[oldLength].id, isIdentity); + subject, verificationsForAccount[oldLength].id, isIdentity, + ); expect(verificationsForAccount[oldLength]) .to.have.property('status', VerificationsStatus.Issued); }); it('can add subverification paths', async () => { const oldLength = (await verifications.getVerifications( - subject, '/company/b-s-s/employee/swo3', isIdentity)).length; + subject, '/company/b-s-s/employee/swo3', isIdentity, + )).length; await verifications.setVerification(accounts[0], accounts[0], '/company', ...extraArgs); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s', ...extraArgs, + ); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs, + ); await verifications.setVerification( - accounts[0], subject, '/company/b-s-s/employee/swo3', ...extraArgs); + accounts[0], subject, '/company/b-s-s/employee/swo3', ...extraArgs, + ); const verificationsForAccount = await verifications.getVerifications( - subject, '/company/b-s-s/employee/swo3', isIdentity); + subject, '/company/b-s-s/employee/swo3', isIdentity, + ); expect(verificationsForAccount).to.have.lengthOf(1); expect(verificationsForAccount[oldLength]) .to.have.property('status', VerificationsStatus.Issued); @@ -849,17 +924,22 @@ describe('Verifications handler', function() { it('can confirm a subverification paths with the subject user', async () => { const oldLength = (await verifications.getVerifications( - subject, '/company/b-s-s/employee/swo4', isIdentity)).length; + subject, '/company/b-s-s/employee/swo4', isIdentity, + )).length; await verifications.setVerification(accounts[0], accounts[0], '/company', ...extraArgs); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s', ...extraArgs, + ); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs, + ); const verificationId = await verifications.setVerification( - accounts[0], subject, '/company/b-s-s/employee/swo4', ...extraArgs); + accounts[0], subject, '/company/b-s-s/employee/swo4', ...extraArgs, + ); await verifications.confirmVerification(accounts[0], subject, verificationId, isIdentity); const verificationsForAccount = await verifications.getVerifications( - subject, '/company/b-s-s/employee/swo4', isIdentity); + subject, '/company/b-s-s/employee/swo4', isIdentity, + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); expect(verificationsForAccount[oldLength]) .to.have.property('status', VerificationsStatus.Confirmed); @@ -868,23 +948,28 @@ describe('Verifications handler', function() { it('can delete a subverification path with the subject user', async () => { await verifications.setVerification(accounts[0], accounts[0], '/company', ...extraArgs); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s', ...extraArgs, + ); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs, + ); const verificationId = await verifications.setVerification( - accounts[0], subject, '/company/b-s-s/employee/swo6', ...extraArgs); + accounts[0], subject, '/company/b-s-s/employee/swo6', ...extraArgs, + ); await verifications.deleteVerification(accounts[0], subject, verificationId, isIdentity); const verificationsForAccount = await verifications.getVerifications( - subject, '/company/b-s-s/employee/swo6', isIdentity); + subject, '/company/b-s-s/employee/swo6', isIdentity, + ); expect(verificationsForAccount).to.have.lengthOf(0); }); - it('can track the creation date', async() => { + it('can track the creation date', async () => { const before = Math.floor(Date.now() / 1000); await verifications.setVerification(accounts[0], subject, '/company', ...extraArgs); const after = Math.floor(Date.now() / 1000); const verificationsForAccount = await verifications.getVerifications( - subject, '/company', isIdentity); + subject, '/company', isIdentity, + ); const last = verificationsForAccount.length - 1; expect(verificationsForAccount[last]) .to.have.property('status', VerificationsStatus.Issued); @@ -893,37 +978,42 @@ describe('Verifications handler', function() { expect(parseInt(verificationsForAccount[last].creationDate, 10)).to.be.lte(after); }); - it('can track the expiration date and the expired flag is set correctly', async() => { + it('can track the expiration date and the expired flag is set correctly', async () => { const before = Math.floor(Date.now() / 1000); await verifications.setVerification( - accounts[0], subject, '/company', before, ...extraArgs.slice(1)); + accounts[0], subject, '/company', before, ...extraArgs.slice(1), + ); const after = Math.floor(Date.now() / 1000); const verificationsForAccount = await verifications.getVerifications( - subject, '/company', isIdentity); + subject, '/company', isIdentity, + ); const last = verificationsForAccount.length - 1; expect(verificationsForAccount[last]).to.have.property( - 'status', VerificationsStatus.Issued); + 'status', VerificationsStatus.Issued, + ); expect(verificationsForAccount[last]).to.have.property('expirationDate'); expect(parseInt(verificationsForAccount[last].expirationDate, 10)).to.be.eq(before); expect(parseInt(verificationsForAccount[last].expirationDate, 10)).to.be.lte(after); expect(verificationsForAccount[last].expired).to.be.eq(true); }); - it('can track the creation block', async() => { + it('can track the creation block', async () => { const before = await web3.eth.getBlockNumber(); await verifications.setVerification(accounts[0], subject, '/company', ...extraArgs); const after = await web3.eth.getBlockNumber(); const verificationsForAccount = await verifications.getVerifications( - subject, '/company', isIdentity); + subject, '/company', isIdentity, + ); const last = verificationsForAccount.length - 1; expect(verificationsForAccount[last]).to.have.property( - 'status', VerificationsStatus.Issued); + 'status', VerificationsStatus.Issued, + ); expect(verificationsForAccount[last]).to.have.property('creationBlock'); expect(parseInt(verificationsForAccount[last].creationBlock, 10)).to.be.gte(before); expect(parseInt(verificationsForAccount[last].creationBlock, 10)).to.be.lte(after); }); - it('can add a description to a verification', async() => { + it('can add a description to a verification', async () => { const sampleVerificationsDomain = 'sample'; const sampleVerificationTopic = '/company'; const sampleDescription = { @@ -934,7 +1024,8 @@ describe('Verifications handler', function() { dbcpVersion: 1, }; await verifications.setVerificationDescription( - accounts[0], sampleVerificationTopic, sampleVerificationsDomain, sampleDescription); + accounts[0], sampleVerificationTopic, sampleVerificationsDomain, sampleDescription, + ); await verifications.setVerification( accounts[0], subject, @@ -945,28 +1036,36 @@ describe('Verifications handler', function() { ...extraArgs.slice(3), ); const verificationsForAccount = await verifications.getVerifications( - subject, sampleVerificationTopic, isIdentity); + subject, sampleVerificationTopic, isIdentity, + ); const last = verificationsForAccount.length - 1; expect(verificationsForAccount[last]).to.have.property( - 'status', VerificationsStatus.Issued); + 'status', VerificationsStatus.Issued, + ); expect(verificationsForAccount[last]).to.have.property('creationBlock'); expect(verificationsForAccount[last].description).to.deep.eq(sampleDescription); }); it('can reject a verification', async () => { const oldLength = (await verifications.getVerifications( - subject, '/company/b-s-s/employee/swo4', isIdentity)).length; + subject, '/company/b-s-s/employee/swo4', isIdentity, + )).length; await verifications.setVerification(accounts[0], accounts[0], '/company', ...extraArgs); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s', ...extraArgs, + ); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs, + ); const verificationId = await verifications.setVerification( - accounts[0], subject, '/company/b-s-s/employee/swo4', ...extraArgs); + accounts[0], subject, '/company/b-s-s/employee/swo4', ...extraArgs, + ); await verifications.rejectVerification( - accounts[0], subject, verificationId, 0, isIdentity); + accounts[0], subject, verificationId, 0, isIdentity, + ); const verificationsForAccount = await verifications.getVerifications( - subject, '/company/b-s-s/employee/swo4', isIdentity); + subject, '/company/b-s-s/employee/swo4', isIdentity, + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); expect(verificationsForAccount[oldLength]) .to.have.property('status', VerificationsStatus.Rejected); @@ -974,21 +1073,27 @@ describe('Verifications handler', function() { it('can reject a verification with a reason', async () => { const oldLength = (await verifications.getVerifications( - subject, '/company/b-s-s/employee/swo4', isIdentity)).length; + subject, '/company/b-s-s/employee/swo4', isIdentity, + )).length; await verifications.setVerification(accounts[0], accounts[0], '/company', ...extraArgs); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s', ...extraArgs, + ); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs, + ); await timeout(1000); const verificationId = await verifications.setVerification( - accounts[0], subject, '/company/b-s-s/employee/swo4', ...extraArgs); + accounts[0], subject, '/company/b-s-s/employee/swo4', ...extraArgs, + ); await timeout(1000); await verifications.rejectVerification( - accounts[0], subject, verificationId, { reason: 'denied' }, isIdentity); + accounts[0], subject, verificationId, { reason: 'denied' }, isIdentity, + ); await timeout(1000); const verificationsForAccount = await verifications.getVerifications( - subject, '/company/b-s-s/employee/swo4', isIdentity); + subject, '/company/b-s-s/employee/swo4', isIdentity, + ); expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); expect(verificationsForAccount[oldLength]) .to.have.property('status', VerificationsStatus.Rejected); @@ -999,13 +1104,17 @@ describe('Verifications handler', function() { it('can not re accept a rejected verification', async () => { await verifications.setVerification(accounts[0], accounts[0], '/company', ...extraArgs); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s', ...extraArgs, + ); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs, + ); const verificationId = await verifications.setVerification( - accounts[0], subject, '/company/b-s-s/employee/swo4', ...extraArgs); + accounts[0], subject, '/company/b-s-s/employee/swo4', ...extraArgs, + ); await verifications.rejectVerification( - accounts[0], subject, verificationId, 0, isIdentity); + accounts[0], subject, verificationId, 0, isIdentity, + ); const reacceptedP = verifications.confirmVerification(accounts[0], subject, verificationId); await expect(reacceptedP).to.be.rejected; }); @@ -1014,20 +1123,23 @@ describe('Verifications handler', function() { async () => { await verifications.setVerification(accounts[0], accounts[0], '/company', ...extraArgs); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s', ...extraArgs, + ); await verifications.setVerification( - accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs); + accounts[0], accounts[0], '/company/b-s-s/employee', ...extraArgs, + ); const verificationId = await verifications.setVerification( - accounts[0], subject, '/company/b-s-s/employee/swo4', ...extraArgs); + accounts[0], subject, '/company/b-s-s/employee/swo4', ...extraArgs, + ); await expect(verifications.confirmVerification(accounts[1], subject, verificationId)) .to.be.rejected; - } - ); + }); describe('when validating nested verifications', () => { it('non existing verifications include the warning "missing" and status should be -1', async () => { - let topic = getRandomTopic('/evan'), computed; + let computed; + const topic = getRandomTopic('/evan'); // check missing state computed = await verifications.getComputedVerification(subject, topic, isIdentity); @@ -1037,15 +1149,13 @@ describe('Verifications handler', function() { // check missing state is missing after set await verifications.setVerification(accounts[0], subject, topic, ...extraArgs); computed = await verifications.getComputedVerification(subject, topic, isIdentity); - const v2 = await verifications.getNestedVerificationsV2(subject, topic, isIdentity, queryOptions); await expect(computed.status).to.be.eq(0); await expect(computed.warnings).to.not.include('missing'); - } - ); + }); it('should parent', async () => { - let parentTopic = getRandomTopic(''); - let topic = getRandomTopic(parentTopic); + const parentTopic = getRandomTopic(''); + const topic = getRandomTopic(parentTopic); // check missing state let computed; @@ -1057,14 +1167,15 @@ describe('Verifications handler', function() { await verifications.setVerification(accounts[0], subject, parentTopic, ...extraArgs); await verifications.setVerification(accounts[0], subject, topic, ...extraArgs); - await new Promise(s => setTimeout(s, 10000)); + await new Promise((s) => setTimeout(s, 10000)); // load parent verifications and computed from child computed = await verifications.getComputedVerification(subject, topic, isIdentity); }); it('verifications with status 0 should have warning "issued"', async () => { - let topic = getRandomTopic('/evan'), computed; + let computed; + const topic = getRandomTopic('/evan'); // check issued case await verifications.setVerification(accounts[0], subject, topic, ...extraArgs); @@ -1083,7 +1194,8 @@ describe('Verifications handler', function() { const topic = getRandomTopic('/evan'); const before = Math.floor(Date.now() / 1000); await verifications.setVerification( - accounts[0], subject, topic, before, ...extraArgs.slice(1)); + accounts[0], subject, topic, before, ...extraArgs.slice(1), + ); const computed = await verifications.getComputedVerification(subject, topic, isIdentity); await expect(computed.status).to.be.eq(0); await expect(computed.warnings).to.include('expired'); @@ -1092,8 +1204,8 @@ describe('Verifications handler', function() { it('verifications with a missing parent should have the warning "parentMissing"', async () => { let computed; - let topicParent = getRandomTopic(''); - let topic = getRandomTopic(topicParent); + const topicParent = getRandomTopic(''); + const topic = getRandomTopic(topicParent); // check issued case await verifications.setVerification(accounts[0], subject, topic, ...extraArgs); @@ -1103,49 +1215,50 @@ describe('Verifications handler', function() { await verifications.setVerification(accounts[0], accounts[0], topicParent); computed = await verifications.getComputedVerification(subject, topic, isIdentity); await expect(computed.warnings).to.not.include('parentMissing'); - } - ); + }); it('verifications with an untrusted parent should have the warning "parentUntrusted"', async () => { - let computed; - let topicParent = getRandomTopic('/evan'); - let topic = getRandomTopic(topicParent); + const topicParent = getRandomTopic('/evan'); + const topic = getRandomTopic(topicParent); // check issued case await verifications.setVerification(accounts[0], subject, topic, ...extraArgs); await verifications.setVerification(accounts[0], accounts[0], topicParent); - computed = await verifications.getComputedVerification(subject, topic, isIdentity); + const computed = await verifications.getComputedVerification( + subject, topic, isIdentity, + ); await expect(computed.warnings).to.include('parentUntrusted'); - } - ); + }); it('verifications with the base "/evan" should be issued by the evan root account', async () => { - let computed; - let topic = '/evan'; + const topic = '/evan'; // check issued case await verifications.setVerification(accounts[0], subject, topic, ...extraArgs); - computed = await verifications.getComputedVerification(subject, topic, isIdentity); + const computed = await verifications.getComputedVerification( + subject, topic, isIdentity, + ); await expect(computed.warnings).to.include('notEnsRootOwner'); - } - ); + }); - it('sub verifications, where the parent verifications has the property has ' + - '"disableSubVerifications" should be not valid', + it('sub verifications, where the parent verifications has the property has ' + + '"disableSubVerifications" should be not valid', async () => { - let parentTopic = getRandomTopic(''); - let topic = getRandomTopic(parentTopic); + const parentTopic = getRandomTopic(''); + const topic = getRandomTopic(parentTopic); // check issued case await verifications.setVerification( - accounts[0], accounts[0], parentTopic, 0, null, null, true); + accounts[0], accounts[0], parentTopic, 0, null, null, true, + ); await verifications.setVerification(accounts[0], subject, topic, ...extraArgs); // load parent verifications and computed from child const parentComputed = await verifications.getComputedVerification( - accounts[0], parentTopic); + accounts[0], parentTopic, + ); const computed = await verifications.getComputedVerification(subject, topic, isIdentity); await expect(parentComputed.disableSubVerifications).to.be.eq(true); @@ -1161,7 +1274,8 @@ describe('Verifications handler', function() { before(async () => { verificationsRegistry = await executor.createContract( - 'VerificationsRegistry', [], { from: accounts[2], gas: 8000000 }); + 'VerificationsRegistry', [], { from: accounts[2], gas: 8000000 }, + ); verifications.contracts.registry = verificationsRegistry; contractId = await baseContract.createUninitialized( @@ -1185,7 +1299,7 @@ describe('Verifications handler', function() { context.subject = contractId; }); - it('can create a new identity for a contract', async() => { + it('can create a new identity for a contract', async () => { const identity = await verifications.createIdentity(accounts[0], contractId); expect(identity).to.match(/0x[0-9-a-f]{64}/i); }); @@ -1194,7 +1308,7 @@ describe('Verifications handler', function() { it('does not return verification data, when identity and contract id mismatch', async () => { // create two contracts with a verification - const [ contractId1, contractId2 ] = await Promise.all([...Array(2)].map(async () => { + const [contractId1, contractId2] = await Promise.all([...Array(2)].map(async () => { const localContractId = await baseContract.createUninitialized( 'testdatacontract', accounts[0], @@ -1218,7 +1332,8 @@ describe('Verifications handler', function() { // each contract should have one verification const contractVerifications = await verifications.getVerifications( - localContractId, '/company'); + localContractId, '/company', + ); expect(contractVerifications).to.have.lengthOf(1); expect(contractVerifications[0]).to.have.property('status', VerificationsStatus.Issued); @@ -1237,7 +1352,7 @@ describe('Verifications handler', function() { }); it('verifications for a subject that has no identity should throw', async () => { - let topic = getRandomTopic('/evan'); + const topic = getRandomTopic('/evan'); const contractIdWithoutIdentity = await baseContract.createUninitialized( 'testdatacontract', @@ -1277,7 +1392,8 @@ describe('Verifications handler', function() { { from: accounts[0], gas: 500000 }, ); undescribedIdentity = await verifications.createIdentity( - accounts[0], undescribedContract.options.address, false); + accounts[0], undescribedContract.options.address, false, + ); context.subject = undescribedIdentity; }); @@ -1285,7 +1401,8 @@ describe('Verifications handler', function() { it('throws an error when trying to set an identity on a contractId ', async () => { const setPromise = verifications.setVerification( - accounts[0], undescribedContract.options.address, '/company', 0, null, null, false, true); + accounts[0], undescribedContract.options.address, '/company', 0, null, null, false, true, + ); await expect(setPromise).to.be.rejected; }); }); @@ -1302,7 +1419,8 @@ describe('Verifications handler', function() { // on account[0]s side // accounts[0] wants to issue a verification for accounts[1] via delegation const txInfo = await verifications.signSetVerificationTransaction( - accounts[0], accounts[1], topic); + accounts[0], accounts[1], topic, + ); // on account[2]s side // accounts[2] submits transaction, that actually issues verification @@ -1325,7 +1443,8 @@ describe('Verifications handler', function() { // on account[0]s side const txInfo = await verifications.signSetVerificationTransaction( - accounts[0], accounts[1], topic); + accounts[0], accounts[1], topic, + ); // on account[2]s side const verificationId = await verifications.executeVerification(accounts[2], txInfo); @@ -1336,8 +1455,7 @@ describe('Verifications handler', function() { expect(verificationsForAccount).to.have.lengthOf(oldLength + 1); expect(verificationsForAccount[oldLength]) .to.have.property('status', VerificationsStatus.Issued); - } - ); + }); it('allows to get execution nonce for a given identity', async () => { const nonce1 = await verifications.getExecutionNonce(accounts[0]); @@ -1355,26 +1473,29 @@ describe('Verifications handler', function() { it('allows to submit multiple "cold" transactions from another account', async () => { const paths = ['/verfication1', '/verfication2', '/verfication3']; - const oldLengths = - (await Promise.all(paths.map(path => verifications.getVerifications(accounts[1], path)))) - .map(veris => veris.length); + const oldLengths = (await Promise.all( + paths.map((path) => verifications.getVerifications(accounts[1], path)), + )).map((veris) => veris.length); await timeout(1000); // on account[0]s side let nonce = JSON.parse(await verifications.getExecutionNonce(accounts[0])); - let txInfos = []; - for (let path of paths) { + const txInfos = []; + for (const path of paths) { txInfos.push(await verifications.signSetVerificationTransaction( - accounts[0], accounts[1], path, 0, null, null, false, false, nonce++)); + accounts[0], accounts[1], path, 0, null, null, false, false, nonce, + )); + nonce += 1; } // on account[2]s side - for (let i of txInfos.keys()) { + for (const i of txInfos.keys()) { const verificationId = await verifications.executeVerification(accounts[2], txInfos[i]); await timeout(1000); expect(verificationId).to.be.ok; const verificationsForAccount = await verifications.getVerifications( - accounts[1], `/verfication${i + 1}`); + accounts[1], `/verfication${i + 1}`, + ); expect(verificationsForAccount).to.have.lengthOf(oldLengths[i] + 1); expect(verificationsForAccount[oldLengths[i]]) .to.have.property('status', VerificationsStatus.Issued); @@ -1386,7 +1507,8 @@ describe('Verifications handler', function() { it('can prepare transactions and submit them with another account', async () => { // create test contract const testContract = await executor.createContract( - 'TestContract', ['old data'], { from: accounts[0], gas: 500e3 }); + 'TestContract', ['old data'], { from: accounts[0], gas: 500e3 }, + ); let data = await executor.executeContractCall(testContract, 'data'); expect(data).to.eq('old data'); @@ -1411,7 +1533,8 @@ describe('Verifications handler', function() { it('can prepare transactions and submit them with the same account', async () => { // create test contract const testContract = await executor.createContract( - 'TestContract', ['old data'], { from: accounts[0], gas: 500e3 }); + 'TestContract', ['old data'], { from: accounts[0], gas: 500e3 }, + ); let data = await executor.executeContractCall(testContract, 'data'); expect(data).to.eq('old data'); @@ -1436,7 +1559,8 @@ describe('Verifications handler', function() { it('can handle events when submitting transactions', async () => { // create test contract const testContract = await executor.createContract( - 'TestContractEvent', [], { from: accounts[0], gas: 500e3 }); + 'TestContractEvent', [], { from: accounts[0], gas: 500e3 }, + ); // on account[0]s side const txInfo = await verifications.signTransaction( @@ -1454,7 +1578,7 @@ describe('Verifications handler', function() { eventName: 'EventFired', contract: testContract, }, - getEventResult: (_, args) => { return args.fired; }, + getEventResult: (_, args) => args.fired, }, ); diff --git a/src/verifications/verifications.ts b/src/verifications/verifications.ts index f5661e18..236d33f0 100644 --- a/src/verifications/verifications.ts +++ b/src/verifications/verifications.ts @@ -17,22 +17,28 @@ the following URL: https://evan.network/license/ */ -import crypto = require('crypto'); -import prottle = require('prottle'); import { BigNumber } from 'bignumber.js'; import { AccountStore, ContractLoader, - Description, Executor, Logger, LoggerOptions, NameResolver, - DfsInterface + DfsInterface, } from '@evan.network/dbcp'; -import { Ipfs } from '../dfs/ipfs'; -import { nullAddress, nullBytes32 } from '../common/utils'; +import { + nullAddress, + nullBytes32, +} from '../common/utils'; +import { + Description, + Ipfs, +} from '../index'; + +import crypto = require('crypto'); +import prottle = require('prottle'); /** @@ -211,7 +217,8 @@ export interface VerificationsVerificationEntry { * * @param {Partial} verification current verification result * (without status) - * @param {Partial} partialResult options for verifications query + * @param {Partial} partialResult options for verifications + * query * @return {Promise} status for this verification */ export interface VerificationsVerificationEntryStatusComputer { @@ -225,7 +232,8 @@ export interface VerificationsVerificationEntryStatusComputer { * Computes status from overall verifications result. * This function is applied after each verification has received an own computed status. * - * @param {Partial} partialResult current verification result (without status) + * @param {Partial} partialResult current verification result + * (without status) * @param {VerificationsQueryOptions} queryOptions options for verifications query * @param {VerificationsStatusV2} currentStatus current status of verification * @return {Promise} updated status, will be used at verification status @@ -273,32 +281,42 @@ export interface VerificationsOptions extends LoggerOptions { export class Verifications extends Logger { public readonly defaultValidationOptions: VerificationsValidationOptions = { disableSubVerifications: VerificationsStatusV2.Red, - expired: VerificationsStatusV2.Red, - invalid: VerificationsStatusV2.Red, - issued: VerificationsStatusV2.Red, - missing: VerificationsStatusV2.Red, - noIdentity: VerificationsStatusV2.Red, - notEnsRootOwner: VerificationsStatusV2.Red, - parentMissing: VerificationsStatusV2.Red, - parentUntrusted: VerificationsStatusV2.Red, - rejected: VerificationsStatusV2.Red, - selfIssued: VerificationsStatusV2.Red, + expired: VerificationsStatusV2.Red, + invalid: VerificationsStatusV2.Red, + issued: VerificationsStatusV2.Red, + missing: VerificationsStatusV2.Red, + noIdentity: VerificationsStatusV2.Red, + notEnsRootOwner: VerificationsStatusV2.Red, + parentMissing: VerificationsStatusV2.Red, + parentUntrusted: VerificationsStatusV2.Red, + rejected: VerificationsStatusV2.Red, + selfIssued: VerificationsStatusV2.Red, }; + public readonly defaultQueryOptions: VerificationsQueryOptions = { validationOptions: this.defaultValidationOptions, }; + public cachedIdentities: any = { }; + public contracts: any = { }; + public encodingEnvelope = 'binary'; + /** cache all the ens owners */ public ensOwners: any = { }; + public options: VerificationsOptions; + /** check if currently the storage is ensuring, if yes, don't run it twice */ public storageEnsuring: Promise; + public subjectTypes: any = { }; + /** cache all the verifications using an object of promises, to be sure, that the verification is * loaded only once */ public verificationCache: any = { }; + /** backup already loaded verification descriptions */ public verificationDescriptions: any = { }; @@ -317,11 +335,13 @@ export class Verifications extends Logger { if (options.storage) { this.contracts.storage = this.options.contractLoader.loadContract( - 'V00_UserRegistry', options.storage); + 'V00_UserRegistry', options.storage, + ); } if (options.registry) { this.contracts.registry = this.options.contractLoader.loadContract( - 'VerificationsRegistry', options.registry); + 'VerificationsRegistry', options.registry, + ); } } @@ -348,14 +368,15 @@ export class Verifications extends Logger { const computed: any = { creationDate: null, disableSubVerifications: verifications.filter( - verification => verification.disableSubVerifications).length > 0, + (verification) => verification.disableSubVerifications, + ).length > 0, displayName: topic.split('/').pop() || 'evan', - loading: verifications.filter(verification => verification.loading).length > 0, + loading: verifications.filter((verification) => verification.loading).length > 0, name: topic, status: -1, - subjects: [ ], - verifications: verifications, - warnings: [ ], + subjects: [], + verifications, + warnings: [], }; // load the description for the given topic @@ -363,12 +384,16 @@ export class Verifications extends Logger { // keep creationDates of all verifications, so we can check after the final combined status was // set, which creation date should be used - const creationDates = { '-1': [ ], '0': [ ], '1': [ ], '2': [ ]}; - const expirationDates = { '-1': [ ], '0': [ ], '1': [ ], '2': [ ]}; + const creationDates = { + '-1': [], 0: [], 1: [], 2: [], + }; + const expirationDates = { + '-1': [], 0: [], 1: [], 2: [], + }; // iterate through all verifications and check for warnings and the latest creation date of an // verification - for (let verification of verifications) { + for (const verification of verifications) { // concatenate all warnings computed.warnings = computed.warnings.concat(verification.warnings); @@ -377,13 +402,11 @@ export class Verifications extends Logger { if (computed.status === -1) { computed.status = 2; } + } else if (computed.status === 2) { + computed.status = verification.status; } else { - if (computed.status === 2) { - computed.status = verification.status; - } else { - computed.status = computed.status < verification.status ? - verification.status : computed.status; - } + computed.status = computed.status < verification.status + ? verification.status : computed.status; } // search one subject of all @@ -404,7 +427,7 @@ export class Verifications extends Logger { // use the latest creationDate for the specific status if (creationDates[computed.status].length > 0) { - computed.creationDate = creationDates[computed.status].sort()[0]; + [computed.creationDate] = creationDates[computed.status].sort(); } // use the latest creationDate for the specific status @@ -472,18 +495,19 @@ export class Verifications extends Logger { if (!contractId && linkContract) { // create Identity contract const identityContract = await this.options.executor.createContract( - 'VerificationHolder', [ accountId ], { from: accountId, gas: 3000000, }); + 'VerificationHolder', [accountId], { from: accountId, gas: 3000000 }, + ); - const identityStorage = this.contracts.storage.options.address !== nullAddress ? - this.options.contractLoader.loadContract( - 'V00_UserRegistry', this.contracts.storage.options.address) : - null - ; + const identityStorage = this.contracts.storage.options.address !== nullAddress + ? this.options.contractLoader.loadContract( + 'V00_UserRegistry', this.contracts.storage.options.address, + ) + : null; // register the new user in the registry await this.options.executor.executeContractTransaction( identityStorage, 'registerUser', - { from: accountId, }, + { from: accountId }, identityContract.options.address, ); identity = identityContract.options.address; @@ -532,22 +556,23 @@ export class Verifications extends Logger { public deleteFromVerificationCache(subject: string, topic: string) { // prepend starting slash if it does not exists if (topic.indexOf('/') !== 0) { - topic = '/' + topic; + // eslint-disable-next-line no-param-reassign + topic = `/${topic}`; } // search for all parents, that could have links to the topic, so remove them - Object.keys(this.verificationCache).forEach(key => { + Object.keys(this.verificationCache).forEach((key) => { // if the key is equal to the topic that should be checked, delete only the cache for the // given subject if (key === topic) { // delete all related subjects for the given topic, or remove all, when subject is a // wildcard - if (this.verificationCache[topic] && - (this.verificationCache[topic][subject] || subject === '*')) { + if (this.verificationCache[topic] + && (this.verificationCache[topic][subject] || subject === '*')) { delete this.verificationCache[topic][subject]; } - return; + // else remove all child topics } else if (key.indexOf(topic) !== -1) { delete this.verificationCache[key]; @@ -602,8 +627,8 @@ export class Verifications extends Logger { if (!verification.description) { // if the description could not be loaded, the cache will set to false, so we do not need to // load again - if (!this.verificationDescriptions[ensAddress] && - this.verificationDescriptions[ensAddress] !== false) { + if (!this.verificationDescriptions[ensAddress] + && this.verificationDescriptions[ensAddress] !== false) { this.verificationDescriptions[ensAddress] = (async () => { try { // load the description @@ -613,21 +638,24 @@ export class Verifications extends Logger { } })(); } - + // eslint-disable-next-line no-param-reassign verification.description = await this.verificationDescriptions[ensAddress]; } if (verification.description) { // map the properties to a flat description if (verification.description.public) { + // eslint-disable-next-line no-param-reassign verification.description = verification.description.public; } // move the img to the basic verification if (verification.description.imgSquare) { + // eslint-disable-next-line no-param-reassign verification.icon = verification.description.imgSquare; } } else { + // eslint-disable-next-line no-param-reassign verification.description = { author: nullAddress, dbcpVersion: 1, @@ -636,28 +664,34 @@ export class Verifications extends Logger { version: '1.0.0', }; } - + // eslint-disable-next-line no-param-reassign verification.description.i18n = verification.description.i18n || { }; + // eslint-disable-next-line no-param-reassign verification.description.i18n.name = verification.description.i18n.name || { }; - verification.description.i18n.name.en = - verification.description.i18n.name.en || verification.name.split('/').pop(); + // eslint-disable-next-line no-param-reassign + verification.description.i18n.name.en = verification.description.i18n.name.en || verification.name.split('/').pop(); // try to load a clear name try { + // eslint-disable-next-line no-param-reassign verification.displayName = verification.description.i18n.name.en; - } catch (ex) { } + } catch (ex) { + this.log(ex, 'debug'); + } // if the top level ens owner was not loaded before, load it! if (!this.ensOwners[topLevelDomain]) { this.ensOwners[topLevelDomain] = (async () => { // transform the ens domain into a namehash and load the ens top level topic owner - const namehash = this.options.nameResolver.namehash(topLevelDomain + '.verifications.evan'); - return await this.options.executor.executeContractCall( - this.options.nameResolver.ensContract, 'owner', namehash); + const namehash = this.options.nameResolver.namehash(`${topLevelDomain}.verifications.evan`); + return this.options.executor.executeContractCall( + this.options.nameResolver.ensContract, 'owner', namehash, + ); })(); } - + // eslint-disable-next-line no-param-reassign verification.ensAddress = ensAddress; + // eslint-disable-next-line no-param-reassign verification.topLevelEnsOwner = await this.ensOwners[topLevelDomain]; } @@ -672,9 +706,9 @@ export class Verifications extends Logger { */ public async executeVerification( accountId: string, - txInfo: VerificationsDelegationInfo + txInfo: VerificationsDelegationInfo, ): Promise { - return await this.executeTransaction( + return this.executeTransaction( accountId, txInfo, { @@ -683,7 +717,8 @@ export class Verifications extends Logger { targetAddress: txInfo.targetIdentity, eventName: 'VerificationAdded', contract: this.options.contractLoader.loadContract( - 'VerificationHolderLibrary', txInfo.targetIdentity), + 'VerificationHolderLibrary', txInfo.targetIdentity, + ), }, getEventResult: (_, args) => args.verificationId, }, @@ -700,7 +735,8 @@ export class Verifications extends Logger { * @param {VerificationsDelegationInfo} txInfo details about the transaction * @param {} event The event * @param {Function} getEventResult The get event result - * @param {any} partialOptions (optional) data for handling event triggered by this transaction + * @param {any} partialOptions (optional) data for handling event + * triggered by this transaction */ public async executeTransaction( accountId: string, @@ -716,12 +752,9 @@ export class Verifications extends Logger { targetIdentity, } = txInfo; - const transactionTarget = to ? - to : // to given directly - targetIdentity.length === 42 ? - targetIdentity : // target identity contract given - this.contracts.registry.options.address // contract/pseudonym identity given - ; + const transactionTarget = to || (targetIdentity.length === 42 + ? targetIdentity // target identity contract given + : this.contracts.registry.options.address); // contract/pseudonym identity given return this.executeAndHandleEventResult( accountId, @@ -746,9 +779,9 @@ export class Verifications extends Logger { * displayName */ public async getComputedVerification(subject: string, topic: string, isIdentity?: boolean) { - return await this.computeVerifications( + return this.computeVerifications( topic, - await this.getNestedVerifications(subject, topic, isIdentity) + await this.getNestedVerifications(subject, topic, isIdentity), ); } @@ -764,7 +797,8 @@ export class Verifications extends Logger { await this.ensureStorage(); const identity = isIdentity ? issuer : await this.getIdentityForAccount(issuer, true); const identityContract = this.options.contractLoader.loadContract( - 'VerificationHolder', identity); + 'VerificationHolder', identity, + ); return this.options.executor.executeContractCall(identityContract, 'getExecutionNonce'); } @@ -789,15 +823,16 @@ export class Verifications extends Logger { if (targetIdentity !== nullAddress) { this.subjectTypes[subject] = 'account'; this.cachedIdentities[subject] = this.options.contractLoader.loadContract( - 'VerificationHolder', targetIdentity); + 'VerificationHolder', targetIdentity, + ); } else { let description; try { description = await this.options.description.getDescription(subject, null); } catch (_) { throw new Error( - `could not get identity for "${subject}" use either an account with an identity, ` + - 'a contract with a description or work with the 32Bytes identity instead of contractId', + `could not get identity for "${subject}" use either an account with an identity, ` + + 'a contract with a description or work with the 32Bytes identity instead of contractId', ); } @@ -810,11 +845,12 @@ export class Verifications extends Logger { // we got an identity from description, now check, that contract id matches // linked address const linked = await this.options.executor.executeContractCall( - this.contracts.registry, 'getLink', description.public.identity); + this.contracts.registry, 'getLink', description.public.identity, + ); if (!(new RegExp(`${subject.substr(2)}$`, 'i')).test(linked)) { - const msg = `subject description of "${subject}" points to identity ` + - `"${description.public.identity}", but this identity is linked to address ` + - `"${linked}"`; + const msg = `subject description of "${subject}" points to identity ` + + `"${description.public.identity}", but this identity is linked to address ` + + `"${linked}"`; this.log(msg, 'error'); throw new Error(msg); } @@ -834,9 +870,8 @@ export class Verifications extends Logger { if (onlyAddress && this.cachedIdentities[subject].options) { return this.cachedIdentities[subject].options.address; - } else { - return this.cachedIdentities[subject]; } + return this.cachedIdentities[subject]; } /** @@ -908,7 +943,8 @@ export class Verifications extends Logger { public async getNestedVerifications(subject: string, topic: string, isIdentity?: boolean) { // prepend starting slash if it does not exist if (topic.indexOf('/') !== 0) { - topic = '/' + topic; + // eslint-disable-next-line no-param-reassign + topic = `/${topic}`; } // if no storage was ensured before, run it only once @@ -919,7 +955,7 @@ export class Verifications extends Logger { if (!this.verificationCache[topic][subject]) { // load the verifications and store promise within the verification cache object this.verificationCache[topic][subject] = (async () => { - let verifications = [ ]; + let verifications = []; let subjectIdentity; if (isIdentity) { @@ -928,7 +964,7 @@ export class Verifications extends Logger { try { subjectIdentity = await this.getIdentityForAccount(subject, true); } catch (ex) { - verifications = [ ]; + verifications = []; } } if (subjectIdentity !== nullAddress) { @@ -937,36 +973,44 @@ export class Verifications extends Logger { if (verifications.length > 0) { // build display name for verifications and apply computed states for ui status - await prottle(10, verifications.map(verification => async () => { + await prottle(10, verifications.map((verification) => async () => { const splitName = verification.name.split('/'); - + // eslint-disable-next-line no-param-reassign verification.displayName = splitName.pop(); + // eslint-disable-next-line no-param-reassign verification.parent = splitName.join('/'); - verification.warnings = [ ]; - verification.creationDate = verification.creationDate * 1000; + // eslint-disable-next-line no-param-reassign + verification.warnings = []; + // eslint-disable-next-line no-param-reassign + verification.creationDate *= 1000; // if expiration date is given, format the unix timestamp if (verification.expirationDate) { - verification.expirationDate = verification.expirationDate * 1000; + // eslint-disable-next-line no-param-reassign + verification.expirationDate *= 1000; } // recover the original account id for the identity issuer + // eslint-disable-next-line no-param-reassign verification.subjectIdentity = subjectIdentity; if (this.subjectTypes[subject] === 'contract') { + // eslint-disable-next-line no-param-reassign verification.subjectType = 'contract'; + // eslint-disable-next-line no-param-reassign verification.subjectOwner = await this.options.executor.executeContractCall( await this.options.contractLoader.loadContract('BaseContract', subject), - 'owner' + 'owner', ); } else { + // eslint-disable-next-line no-param-reassign verification.subjectType = 'account'; } const dataHash = this.options.nameResolver .soliditySha3(verification.subjectIdentity, verification.topic, verification.data) - .replace('0x', '') - ; + .replace('0x', ''); + // eslint-disable-next-line no-param-reassign verification.issuerAccount = this.options.executor.web3.eth.accounts .recover(dataHash, verification.signature); @@ -988,8 +1032,8 @@ export class Verifications extends Logger { // if issuer === subject and only if a parent is passed, so if the root one is empty // and no slash is available - if (verification.issuerAccount === verification.subject && verification.parent && - verification.issuerAccount !== this.options.config.ensRootOwner) { + if (verification.issuerAccount === verification.subject && verification.parent + && verification.issuerAccount !== this.options.config.ensRootOwner) { verification.warnings.push('selfIssued'); } @@ -999,11 +1043,13 @@ export class Verifications extends Logger { if (verification.parent) { // load all sub verifications + // eslint-disable-next-line no-param-reassign verification.parents = await this.getNestedVerifications(verification.issuerAccount, verification.parent, false); // load the computed status of all parent verifications, // to check if the parent tree is valid + // eslint-disable-next-line no-param-reassign verification.parentComputed = await this.computeVerifications(verification.parent, verification.parents); if (verification.parentComputed.status === -1) { @@ -1013,29 +1059,33 @@ export class Verifications extends Logger { } // is the sub verification creation is disabled? - if (verification.parentComputed.disableSubVerifications || - verification.parentComputed.warnings.indexOf('disableSubVerifications') !== -1) { + if (verification.parentComputed.disableSubVerifications + || verification.parentComputed.warnings.indexOf('disableSubVerifications') !== -1) { verification.warnings.push('disableSubVerifications'); } } else { - verification.parents = [ ]; + // eslint-disable-next-line no-param-reassign + verification.parents = []; - if (verification.name === '/evan' && - verification.issuerAccount !== this.options.config.ensRootOwner) { - verification.warnings = [ 'notEnsRootOwner' ]; + if (verification.name === '/evan' + && verification.issuerAccount !== this.options.config.ensRootOwner) { + // eslint-disable-next-line no-param-reassign + verification.warnings = ['notEnsRootOwner']; } else { - const whitelistWarnings = [ 'expired', 'rejected', 'invalid', 'noIdentity', - 'issued' ]; + const whitelistWarnings = ['expired', 'rejected', 'invalid', 'noIdentity', + 'issued']; // if it's a root verification, remove parent, selfIssued and issued warnings - verification.warnings = verification.warnings.filter(warning => - whitelistWarnings.indexOf(warning) !== -1 + // eslint-disable-next-line no-param-reassign + verification.warnings = verification.warnings.filter( + (warning) => whitelistWarnings.indexOf(warning) !== -1, ); } } if (verification.status !== 2) { // set computed status + // eslint-disable-next-line no-param-reassign verification.status = verification.warnings.length > 0 ? 0 : 1; } })); @@ -1043,7 +1093,8 @@ export class Verifications extends Logger { // calculate the computed level around all verifications, // so we can check all verifications for this user (used for issuing) const computed = await this.computeVerifications(topic, verifications); - verifications.forEach(verification => verification.levelComputed = computed); + // eslint-disable-next-line no-param-reassign,no-return-assign + verifications.forEach((verification) => verification.levelComputed = computed); } // if no verifications are available the status would be "no verification issued" @@ -1051,11 +1102,11 @@ export class Verifications extends Logger { verifications.push({ displayName: topic.split('/').pop() || 'evan', name: topic, - parents: [ ], + parents: [], status: -1, - subject: subject, - tree: [ ], - warnings: [ 'missing' ], + subject, + tree: [], + warnings: ['missing'], subjectIdentity: subjectIdentity || nullAddress, }); @@ -1063,7 +1114,7 @@ export class Verifications extends Logger { verifications[0].subjectType = 'contract'; verifications[0].subjectOwner = await this.options.executor.executeContractCall( await this.options.contractLoader.loadContract('BaseContract', subject), - 'owner' + 'owner', ); } else { verifications[0].subjectType = 'account'; @@ -1080,7 +1131,7 @@ export class Verifications extends Logger { })(); } - return await this.verificationCache[topic][subject]; + return this.verificationCache[topic][subject]; } /** @@ -1104,11 +1155,11 @@ export class Verifications extends Logger { return this.formatToV2(nested, queryOptions || this.defaultQueryOptions); } - /** + /** * Builds required data for a transaction from an identity (offchain) and returns data, that can * be used to submit it later on. Return value can be passed to ``executeTransaction``. - * Transaction information is not signed and therefore can only be submitted by an appropriate key - * hold of given identity. + * Transaction information is not signed and therefore can only be submitted by an appropriate + * key hold of given identity. * * Note that, when creating multiple signed transactions, the ``nonce`` argument **has to be * specified and incremented between calls**, as the nonce is included in transaction data and @@ -1132,16 +1183,16 @@ export class Verifications extends Logger { const sourceIdentity = await this.getIdentityForAccount(options.from, true); // fetch nonce as late as possible - const nonce = (typeof options.nonce !== 'undefined' && options.nonce !== -1) ? - `${options.nonce}` : await this.getExecutionNonce(sourceIdentity, true); + const nonce = (typeof options.nonce !== 'undefined' && options.nonce !== -1) + ? `${options.nonce}` : await this.getExecutionNonce(sourceIdentity, true); - const input = contract ? - contract.methods[functionName].apply(contract.methods, args).encodeABI() : - options.input; + const input = contract + ? contract.methods[functionName](...args).encodeABI() + : options.input; - const to = contract ? - contract.options.address : - (options.to || nullAddress); + const to = contract + ? contract.options.address + : (options.to || nullAddress); const value = options.value || 0; @@ -1168,9 +1219,9 @@ export class Verifications extends Logger { // if a reverse domain is available, add it and separate using a dot let domain = 'verifications.evan'; if (clearedTopic.length > 0) { - domain = `${ clearedTopic.split('/').reverse().join('.') }.${ domain }`; + domain = `${clearedTopic.split('/').reverse().join('.')}.${domain}`; } else if (topic.indexOf('/evan') === 0 || topic.indexOf('evan') === 0) { - domain = `evan.${ domain }`; + domain = `evan.${domain}`; } return domain; @@ -1211,57 +1262,57 @@ export class Verifications extends Logger { 'getDisableSubVerifications', 'getVerificationExpirationDate', 'isVerificationRejected', - ].map(fun => this.callOnIdentity(subject, isIdentity, fun, verificationId)); + ].map((fun) => this.callOnIdentity(subject, isIdentity, fun, verificationId)); verificationDetails.push((async () => { const descriptionNodeHash = await this.callOnIdentity( - subject, isIdentity, 'getVerificationDescription', verificationId); + subject, isIdentity, 'getVerificationDescription', verificationId, + ); if (descriptionNodeHash === nullBytes32) { return null; - } else { - const resolverAddress = await this.options.executor.executeContractCall( - this.options.nameResolver.ensContract, 'resolver', descriptionNodeHash); - if (resolverAddress === nullAddress) { - return null; - } else { - const resolver = - this.options.contractLoader.loadContract('PublicResolver', resolverAddress); - const descriptionHash = await this.options.executor.executeContractCall( - resolver, 'content', descriptionNodeHash); - const envelope = (await this.options.dfs.get(descriptionHash)) - .toString(this.encodingEnvelope); - return JSON.parse(envelope).public; - } } + const resolverAddress = await this.options.executor.executeContractCall( + this.options.nameResolver.ensContract, 'resolver', descriptionNodeHash, + ); + if (resolverAddress === nullAddress) { + return null; + } + const resolver = this.options.contractLoader.loadContract('PublicResolver', resolverAddress); + const descriptionHash = await this.options.executor.executeContractCall( + resolver, 'content', descriptionNodeHash, + ); + const envelope = (await this.options.dfs.get(descriptionHash)) + .toString(this.encodingEnvelope); + return JSON.parse(envelope).public; })()); - let [ + const [ verification, verificationStatus, - creationBlock, - creationDate, + creationBlockRaw, + creationDateRaw, disableSubVerifications, - expirationDate, + expirationDateRaw, rejected, description, ] = await Promise.all(verificationDetails); // check BigNumber Objects and convert back - if (expirationDate.toString) { - expirationDate = expirationDate.toString() - } - if (creationBlock.toNumber) { - creationBlock = creationBlock.toNumber() - } - if (creationDate.toString) { - creationDate = creationDate.toString() - } + const expirationDate = expirationDateRaw.toString + ? expirationDateRaw.toString() + : expirationDateRaw; + const creationBlock = creationBlockRaw.toNumber + ? creationBlockRaw.toNumber() + : creationBlockRaw; + const creationDate = creationDateRaw.toString + ? creationDateRaw.toString() + : creationDateRaw; if (verification.issuer === nullAddress) { return false; } - let verificationFlag = verificationStatus ? - VerificationsStatus.Confirmed : VerificationsStatus.Issued; + let verificationFlag = verificationStatus + ? VerificationsStatus.Confirmed : VerificationsStatus.Issued; let rejectReason; if (rejected.rejected) { verificationFlag = VerificationsStatus.Rejected; @@ -1299,7 +1350,7 @@ export class Verifications extends Logger { })); // drop null values - return verifications.filter(el => el); + return verifications.filter((el) => el); } /** @@ -1315,14 +1366,13 @@ export class Verifications extends Logger { const identity = await this.options.executor.executeContractCall( this.contracts.storage, 'users', - subject + subject, ); if (!identity || identity === nullAddress) { return false; - } else { - return true; } + return true; } /** @@ -1348,12 +1398,14 @@ export class Verifications extends Logger { try { const stringified = JSON.stringify(rejectReason); const stateMd5 = crypto.createHash('md5').update(stringified).digest('hex'); + // eslint-disable-next-line no-param-reassign rejectReason = await this.options.dfs.add(stateMd5, Buffer.from(stringified)); } catch (e) { const msg = `error parsing verificationValue -> ${e.message}`; this.log(msg, 'info'); } } else { + // eslint-disable-next-line no-param-reassign rejectReason = nullBytes32; } @@ -1421,30 +1473,28 @@ export class Verifications extends Logger { issuer, subject, topic, - expirationDate, verificationValue, descriptionDomain, - disableSubVerifications, isIdentity, - uri + uri, ); // clear cache for this verification this.deleteFromVerificationCache(subject, topic); // add the verification to the target identity - return await this.executeOnIdentity( + return this.executeOnIdentity( targetIdentity, true, 'addVerificationWithMetadata', { from: issuer, event: { - target: subjectType === 'contract' ? - 'VerificationsRegistryLibrary' : 'VerificationHolderLibrary', + target: subjectType === 'contract' + ? 'VerificationsRegistryLibrary' : 'VerificationHolderLibrary', eventName: 'VerificationAdded', }, - getEventResult: (_, args) => { return args.verificationId; }, + getEventResult: (_, args) => args.verificationId, }, uint256VerificationName, '1', @@ -1471,10 +1521,10 @@ export class Verifications extends Logger { * @return {Promise} resolved when done */ public async setVerificationDescription( - accountId: string, topic: string, domain: string, description: any + accountId: string, topic: string, domain: string, description: any, ): Promise { let toSet = JSON.parse(JSON.stringify(description)); - if (!toSet.hasOwnProperty('public')) { + if (!Object.prototype.hasOwnProperty.call(toSet, 'public')) { toSet = { public: toSet }; } const domainWithHash = this.getFullDescriptionDomainWithHash(topic, domain); @@ -1516,7 +1566,8 @@ export class Verifications extends Logger { // note that issuer is given for signing, as this ACCOUNT is used to sign the message const signedTransactionInfo = await this.signPackedHash( - options.from , [sourceIdentity, nonce, to, value, input]); + options.from, [sourceIdentity, nonce, to, value, input], + ); return { sourceIdentity, @@ -1550,7 +1601,8 @@ export class Verifications extends Logger { * to 'example.verifications.evan' * @param {boolean} disableSubVerifications if true, verifications created under this path * are invalid - * @param {boolean} isIdentity (optional) true if given subject is an identity, defaults to ``false`` + * @param {boolean} isIdentity (optional) true if given subject is an + * identity, defaults to ``false`` * are invalid * @param {number} nonce issuer identities execution nonce, will be * automatically retrieved if if omitted or set to @@ -1587,31 +1639,28 @@ export class Verifications extends Logger { issuer, subject, topic, - expirationDate, verificationValue, descriptionDomain, - disableSubVerifications, isIdentity, uri, ); // sign arguments for on-chain check - const targetIdentityContract = - this.options.contractLoader.loadContract('VerificationHolder', targetIdentity); + const targetIdentityContract = this.options.contractLoader.loadContract('VerificationHolder', targetIdentity); const txInfo = await this.signTransaction( targetIdentityContract, 'addVerificationWithMetadata', { from: issuer, nonce: executionNonce }, - uint256VerificationName, // uint256 _topic, - '1', // uint256 _scheme, - sourceIdentity, // address _issuer, - signature, // bytes _signature, - verificationData, // bytes _data, - verificationDataUrl, // string _uri, - expirationDate, // uint256 _expirationDate, - ensFullNodeHash, // bytes32 _description, - disableSubVerifications, // bool _disableSubVerifications + uint256VerificationName, // uint256 _topic, + '1', // uint256 _scheme, + sourceIdentity, // address _issuer, + signature, // bytes _signature, + verificationData, // bytes _data, + verificationDataUrl, // string _uri, + expirationDate, // uint256 _expirationDate, + ensFullNodeHash, // bytes32 _description, + disableSubVerifications, // bool _disableSubVerifications ); return { @@ -1634,7 +1683,7 @@ export class Verifications extends Logger { public trimToStatusTree(inputResult: VerificationsResultV2): any { const trimmed: any = { status: inputResult.status, - verifications: inputResult.verifications.map(v => ({ + verifications: inputResult.verifications.map((v) => ({ details: { status: v.details.status, topic: inputResult.levelComputed.topic, @@ -1654,33 +1703,37 @@ export class Verifications extends Logger { * @param {string} subject the subject of the verification * @param {string} verificationId verification identifier * @param {boolean} isIdentity optional indicates if the subject is already an identity - * @return {Promise} resolves with true if the verification is valid, otherwise false + * @return {Promise} resolves with true if the verification is valid, + * otherwise false */ public async validateVerification( - subject: string, verificationId: string, isIdentity?: boolean + subject: string, verificationId: string, isIdentity?: boolean, ): Promise { await this.ensureStorage(); - let subjectIdentity = isIdentity ? subject : await this.getIdentityForAccount(subject, true); + const subjectIdentity = isIdentity ? subject : await this.getIdentityForAccount(subject, true); const verification = await this.callOnIdentity( subject, isIdentity, 'getVerification', - verificationId + verificationId, ); const dataHash = this.options.nameResolver.soliditySha3( - subjectIdentity, verification.topic.toString(), verification.data).replace('0x', ''); + subjectIdentity, verification.topic.toString(), verification.data, + ).replace('0x', ''); const recoveredAddress = this.options.executor.web3.eth.accounts.recover( - dataHash, verification.signature); + dataHash, verification.signature, + ); const issuerContract = this.options.contractLoader.loadContract( - 'VerificationHolder', verification.issuer); + 'VerificationHolder', verification.issuer, + ); const keyHasPurpose = await this.options.executor.executeContractCall( issuerContract, 'keyHasPurpose', this.options.nameResolver.soliditySha3(recoveredAddress), - '1' + '1', ); return keyHasPurpose; } @@ -1696,6 +1749,7 @@ export class Verifications extends Logger { * VerificationsRegistry functions)) * @return {Promise} result of called function */ + // eslint-disable-next-line consistent-return private async callOnIdentity( subject: string, isIdentity: boolean, @@ -1711,12 +1765,12 @@ export class Verifications extends Logger { isIdentity ? subject : await this.getIdentityForAccount(subject), ...args, ); - } else if (subjectType === 'account') { + } if (subjectType === 'account') { // account identity return this.options.executor.executeContractCall( - isIdentity ? - this.options.contractLoader.loadContract('VerificationHolder', subject) : - await this.getIdentityForAccount(subject), + isIdentity + ? this.options.contractLoader.loadContract('VerificationHolder', subject) + : await this.getIdentityForAccount(subject), fun, ...args, ); @@ -1738,8 +1792,8 @@ export class Verifications extends Logger { let bestReachableStatus = VerificationsStatusV2.Green; // 'inherit' parent status only if parent actually has verifications - if (partialResult.levelComputed.parents && - partialResult.levelComputed.parents.verifications.length) { + if (partialResult.levelComputed.parents + && partialResult.levelComputed.parents.verifications.length) { bestReachableStatus = partialResult.levelComputed.parents.status; } @@ -1747,31 +1801,33 @@ export class Verifications extends Logger { // iterate over all verifications, then over all flags and update status // later on pick most trustworthy verification as trust level // iterate even if best reachable is 'red', as status is set per verification - for (let verification of partialResult.verifications) { + for (const verification of partialResult.verifications) { // check this levels trustworthiness let currentVerificationStatus; - if (verification.statusFlags && - verification.statusFlags.length) { + if (verification.statusFlags + && verification.statusFlags.length) { // flags found, set to false and start to prove trustworthiness currentVerificationStatus = VerificationsStatusV2.Red; - for (let statusFlag of verification.statusFlags) { + for (const statusFlag of verification.statusFlags) { // current flag is untrusted by default, start checks let tempStatus = VerificationsStatusV2.Red; // use defined status or function for check if (typeof queryOptions.validationOptions[statusFlag] === 'function') { tempStatus = await (queryOptions.validationOptions[statusFlag] as Function)( - verification, partialResult); + verification, partialResult, + ); } else if (typeof queryOptions.validationOptions[statusFlag] === 'string') { tempStatus = queryOptions.validationOptions[statusFlag] as VerificationsStatusV2; } else if (typeof this.defaultValidationOptions[statusFlag] === 'function') { tempStatus = await (this.defaultValidationOptions[statusFlag] as Function)( - verification, partialResult); + verification, partialResult, + ); } else if (typeof this.defaultValidationOptions[statusFlag] === 'string') { tempStatus = this.defaultValidationOptions[statusFlag] as VerificationsStatusV2; } - if (tempStatus === VerificationsStatusV2.Red || - bestReachableStatus === VerificationsStatusV2.Red) { + if (tempStatus === VerificationsStatusV2.Red + || bestReachableStatus === VerificationsStatusV2.Red) { // if one status flag results red status, instant return, other flags does not need to // be checked currentVerificationStatus = VerificationsStatusV2.Red; @@ -1795,10 +1851,10 @@ export class Verifications extends Logger { // bestReachableStatus has already been taken into consideration in last block, // so we can just take status flag here if (partialResult.verifications - .filter(v => v.details.status === VerificationsStatusV2.Green).length) { + .filter((v) => v.details.status === VerificationsStatusV2.Green).length) { status = VerificationsStatusV2.Green; } else if (partialResult.verifications - .filter(v => v.details.status === VerificationsStatusV2.Yellow).length) { + .filter((v) => v.details.status === VerificationsStatusV2.Yellow).length) { status = VerificationsStatusV2.Yellow; } else { status = VerificationsStatusV2.Red; @@ -1823,14 +1879,14 @@ export class Verifications extends Logger { if (!this.storageEnsuring) { this.storageEnsuring = Promise.all([ this.options.storage || this.options.nameResolver - .getAddress(`identities.${ this.options.nameResolver.config.labels.ensRoot }`), + .getAddress(`identities.${this.options.nameResolver.config.labels.ensRoot}`), this.options.registry || this.options.nameResolver - .getAddress(`contractidentities.${ this.options.nameResolver.config.labels.ensRoot }`), + .getAddress(`contractidentities.${this.options.nameResolver.config.labels.ensRoot}`), ]); } // await storage address - const [ identityStorage, contractIdentityStorage ] = await this.storageEnsuring; + const [identityStorage, contractIdentityStorage] = await this.storageEnsuring; if (!this.contracts.storage) { this.contracts.storage = this.options.contractLoader.loadContract('V00_UserRegistry', identityStorage); @@ -1856,6 +1912,7 @@ export class Verifications extends Logger { * @return {Promise} if `eventInfo` and `getEventResults`, result of `getEventResults`, * otherwise void */ + // eslint-disable-next-line consistent-return private async executeAndHandleEventResult( accountId: string, data: string, @@ -1867,9 +1924,9 @@ export class Verifications extends Logger { signedTransactionInfo?: string, ): Promise { // get users identity - const userIdentity = sourceIdentity ? - this.options.contractLoader.loadContract('VerificationHolder', sourceIdentity) : - await this.getIdentityForAccount(accountId); + const userIdentity = sourceIdentity + ? this.options.contractLoader.loadContract('VerificationHolder', sourceIdentity) + : await this.getIdentityForAccount(accountId); // prepare success + result event handling const options = { @@ -1880,7 +1937,8 @@ export class Verifications extends Logger { }; // run tx - const [executionId, blockNumber] = await (this.options.executor.executeContractTransaction as any)(...[ + const { executor } = this.options; + const [executionId, blockNumber] = await (executor.executeContractTransaction as any)(...[ userIdentity, signedTransactionInfo ? 'executeDelegated' : 'execute', options, @@ -1892,47 +1950,51 @@ export class Verifications extends Logger { // fetch result from event // load user identity as a library, to retrieve library events from users identity const keyHolderLibrary = this.options.contractLoader.loadContract( - 'KeyHolderLibrary', userIdentity.options.address); - const [ executed, failed ] = await Promise.all([ + 'KeyHolderLibrary', userIdentity.options.address, + ); + const [executed, failed] = await Promise.all([ keyHolderLibrary.getPastEvents( - 'Executed', { fromBlock: blockNumber, toBlock: blockNumber }), + 'Executed', { fromBlock: blockNumber, toBlock: blockNumber }, + ), keyHolderLibrary.getPastEvents( - 'ExecutionFailed', { fromBlock: blockNumber, toBlock: blockNumber }), + 'ExecutionFailed', { fromBlock: blockNumber, toBlock: blockNumber }, + ), ]); // flatten and filter events on execution id from identity tx - const filtered = [ ...executed, ...failed ].filter( + const filtered = [...executed, ...failed].filter( (event) => { if (event.returnValues && event.returnValues.executionId) { // check if executionId is a BigNumber object if (event.returnValues.executionId.eq) { - return event.returnValues.executionId.eq(executionId) - } else { - // otherwise check normal equality - return event.returnValues.executionId === executionId + return event.returnValues.executionId.eq(executionId); } + // otherwise check normal equality + return event.returnValues.executionId === executionId; } return false; - } + }, ); if (filtered.length && filtered[0].event === 'Executed') { // if execution was successful if (eventInfo) { // if original options had an event property for retrieving event results let targetIdentityEvents = await eventInfo.contract.getPastEvents( - eventInfo.eventName, { fromBlock: blockNumber, toBlock: blockNumber }); + eventInfo.eventName, { fromBlock: blockNumber, toBlock: blockNumber }, + ); targetIdentityEvents = targetIdentityEvents.filter( - event => event.transactionHash === filtered[0].transactionHash); + (event) => event.transactionHash === filtered[0].transactionHash, + ); if (targetIdentityEvents.length) { return getEventResults(targetIdentityEvents[0], targetIdentityEvents[0].returnValues); } } } else if (filtered.length && filtered[0].event === 'ExecutionFailed') { const values = filtered[0].returnValues; - throw new Error('executeOnIdentity failed; ExecutionFailed event was triggered: ' + - `executionId: "${values.executionId}", to: "${values.to}", value: "${values.value}"`); + throw new Error('executeOnIdentity failed; ExecutionFailed event was triggered: ' + + `executionId: "${values.executionId}", to: "${values.to}", value: "${values.value}"`); } else { - throw new Error('executeOnIdentity failed; subject type was \'account\', ' + - 'but no proper identity tx status event could be retrieved'); + throw new Error('executeOnIdentity failed; subject type was \'account\', ' + + 'but no proper identity tx status event could be retrieved'); } } @@ -1947,6 +2009,7 @@ export class Verifications extends Logger { * VerificationsRegistry functions)) * @return {Promise} result of called function */ + // eslint-disable-next-line consistent-return private async executeOnIdentity( subject: string, isIdentity: boolean, @@ -1957,22 +2020,23 @@ export class Verifications extends Logger { const subjectType = await this.getSubjectType(subject, isIdentity); if (subjectType === 'contract') { const targetIdentity = isIdentity ? subject : await this.getIdentityForAccount(subject); - let abiOnRegistry = this.contracts.registry.methods[fun](targetIdentity, ...args).encodeABI(); + const abiOnRegistry = this.contracts.registry.methods[fun](targetIdentity, ...args) + .encodeABI(); if (options.event) { return this.executeAndHandleEventResult( options.from, abiOnRegistry, { contract: this.options.contractLoader.loadContract( - 'VerificationsRegistryLibrary', this.contracts.registry.options.address), + 'VerificationsRegistryLibrary', this.contracts.registry.options.address, + ), eventName: options.event.eventName, }, options.getEventResult, ); - } else { - return this.executeAndHandleEventResult(options.from, abiOnRegistry); } - } else if (subjectType === 'account') { + return this.executeAndHandleEventResult(options.from, abiOnRegistry); + } if (subjectType === 'account') { // account identity let targetIdentityAddress; if (isIdentity) { @@ -1988,65 +2052,70 @@ export class Verifications extends Logger { // get encoded abi for passing it to identity tx const abi = targetIdentity.methods[fun].apply( targetIdentity.methods[fun], - args + args, ).encodeABI(); // backup original event data and set event data for handling identity tx const originalEvent = options.event; const originalGetEventResult = options.getEventResult; + // eslint-disable-next-line no-param-reassign options.event = { // event Approved(uint256 indexed executionId, bool approved); eventName: 'Approved', target: 'KeyHolderLibrary', // VerificationsRegistryLibrary }; - options.getEventResult = (event, eventArgs) => { - return [eventArgs.executionId, event.blockNumber]; - }; + // eslint-disable-next-line no-param-reassign + options.getEventResult = (event, eventArgs) => [eventArgs.executionId, event.blockNumber]; const identity = await this.getIdentityForAccount(options.from); const [executionId, blockNumber] = await this.options.executor.executeContractTransaction( - identity, 'execute', options, targetIdentity.options.address, 0, abi); + identity, 'execute', options, targetIdentity.options.address, 0, abi, + ); const keyHolderLibrary = this.options.contractLoader.loadContract( - 'KeyHolderLibrary', identity.options.address); - const [ executed, failed ] = await Promise.all([ + 'KeyHolderLibrary', identity.options.address, + ); + const [executed, failed] = await Promise.all([ keyHolderLibrary.getPastEvents( - 'Executed', { fromBlock: blockNumber, toBlock: blockNumber }), + 'Executed', { fromBlock: blockNumber, toBlock: blockNumber }, + ), keyHolderLibrary.getPastEvents( - 'ExecutionFailed', { fromBlock: blockNumber, toBlock: blockNumber }), + 'ExecutionFailed', { fromBlock: blockNumber, toBlock: blockNumber }, + ), ]); // flatten and filter events on execution id from identity tx - const filtered = [ ...executed, ...failed ].filter( + const filtered = [...executed, ...failed].filter( (event) => { if (event.returnValues && event.returnValues.executionId) { // check if executionId is a BigNumber object if (event.returnValues.executionId.eq) { - return event.returnValues.executionId.eq(executionId) - } else { - // otherwise check normal equality - return event.returnValues.executionId === executionId + return event.returnValues.executionId.eq(executionId); } + // otherwise check normal equality + return event.returnValues.executionId === executionId; } return false; - } + }, ); if (filtered.length && filtered[0].event === 'Executed') { // if execution was successful if (originalEvent) { // if original options had an event property for retrieving event results const targetIdentityEvents = await targetIdentity.getPastEvents( - originalEvent.eventName, { fromBlock: blockNumber, toBlock: blockNumber }); + originalEvent.eventName, { fromBlock: blockNumber, toBlock: blockNumber }, + ); if (targetIdentityEvents.length) { return originalGetEventResult( - targetIdentityEvents[0], targetIdentityEvents[0].returnValues); + targetIdentityEvents[0], targetIdentityEvents[0].returnValues, + ); } } } else if (filtered.length && filtered[0].event === 'ExecutionFailed') { const values = filtered[0].returnValues; - throw new Error('executeOnIdentity failed; ExecutionFailed event was triggered: ' + - `executionId: "${values.executionId}", to: "${values.to}", value: "${values.value}"`); + throw new Error('executeOnIdentity failed; ExecutionFailed event was triggered: ' + + `executionId: "${values.executionId}", to: "${values.to}", value: "${values.value}"`); } else { - throw new Error('executeOnIdentity failed; subject type was \'account\', ' + - 'but no proper identity tx status event could be retrieved'); + throw new Error('executeOnIdentity failed; subject type was \'account\', ' + + 'but no proper identity tx status event could be retrieved'); } } } @@ -2063,20 +2132,21 @@ export class Verifications extends Logger { queryOptions: VerificationsQueryOptions, ): Promise { const nestedVerifications = nestedVerificationsInput.filter( - verification => verification.status !== -1); + (verification) => verification.status !== -1, + ); if (!nestedVerifications.length) { return { status: VerificationsStatusV2.Red, verifications: [], }; } - let verifications = []; + const verifications = []; let levelComputed: any; if (nestedVerifications.length) { let parents; - if (nestedVerifications[0].parents && - nestedVerifications[0].parents.length) { + if (nestedVerifications[0].parents + && nestedVerifications[0].parents.length) { parents = await this.formatToV2(nestedVerifications[0].parents, queryOptions); } levelComputed = { @@ -2096,7 +2166,7 @@ export class Verifications extends Logger { } // convert verification data - for (let nestedVerification of nestedVerifications) { + for (const nestedVerification of nestedVerifications) { const verification: Partial = { details: { creationDate: nestedVerification.creationDate, @@ -2111,9 +2181,9 @@ export class Verifications extends Logger { }, raw: { creationBlock: nestedVerification.creationBlock, - creationDate: typeof nestedVerification.creationDate === 'number' ? - `${nestedVerification.creationDate}`.replace(/...$/, '') : - nestedVerification.creationDate, + creationDate: typeof nestedVerification.creationDate === 'number' + ? `${nestedVerification.creationDate}`.replace(/...$/, '') + : nestedVerification.creationDate, data: nestedVerification.data, disableSubVerifications: nestedVerification.disableSubVerifications, signature: nestedVerification.signature, @@ -2133,8 +2203,10 @@ export class Verifications extends Logger { } if (nestedVerification.data && nestedVerification.data !== nullBytes32) { verification.details.data = await this.options.dfs.get( - Ipfs.bytes32ToIpfsHash(nestedVerification.data)); + Ipfs.bytes32ToIpfsHash(nestedVerification.data), + ); } + // eslint-disable-next-line ['expirationDate', 'rejectReason'].map((property) => { if (nestedVerification[property]) { verification.details[property] = nestedVerification[property]; @@ -2172,16 +2244,12 @@ export class Verifications extends Logger { * @param {string} subject subject of the verification and the owner of * the verification node * @param {string} topic name of the verification (full path) - * @param {number} expirationDate expiration date, for the verification, defaults - * to `0` (≈does not expire) * @param {any} verificationValue json object which will be stored in the * verification * @param {string} descriptionDomain domain of the verification, this is a subdomain * under 'verifications.evan', so passing * 'example' will link verifications description * to 'example.verifications.evan' - * @param {boolean} disableSubVerifications if true, verifications created under this path - * are invalid * @param {string} uri when given this uri will be stored on * the new verification * @return {any} data for setting verifications @@ -2190,12 +2258,10 @@ export class Verifications extends Logger { issuer: string, subject: string, topic: string, - expirationDate = 0, verificationValue?: any, descriptionDomain?: string, - disableSubVerifications = false, isIdentity = false, - uri = '' + uri = '', ): Promise<{ targetIdentity: string; subjectType: string; @@ -2211,29 +2277,28 @@ export class Verifications extends Logger { let targetIdentity; if (isIdentity) { targetIdentity = subject; + } else if (subjectType === 'contract') { + targetIdentity = (await this.options.description.getDescription( + subject, issuer, + )).public.identity; } else { - if (subjectType === 'contract') { - targetIdentity = (await this.options.description.getDescription( - subject, issuer)).public.identity; - } else { - targetIdentity = await this.options.executor.executeContractCall( - this.contracts.storage, - 'users', - subject - ); - } + targetIdentity = await this.options.executor.executeContractCall( + this.contracts.storage, + 'users', + subject, + ); } // get the issuer identity contract const sourceIdentity = await this.options.executor.executeContractCall( this.contracts.storage, 'users', - issuer + issuer, ); // check if target and source identity are existing if (!targetIdentity || targetIdentity === nullAddress) { - const msg = `trying to set verification ${topic} with account ${issuer}, ` + - `but target identity for account ${subject} does not exist`; + const msg = `trying to set verification ${topic} with account ${issuer}, ` + + `but target identity for account ${subject} does not exist`; this.log(msg, 'error'); throw new Error(msg); } @@ -2243,7 +2308,7 @@ export class Verifications extends Logger { const uint256VerificationName = new BigNumber(sha3VerificationName).toString(10); let verificationData = nullBytes32; - let verificationDataUrl = uri; + const verificationDataUrl = uri; if (verificationValue) { try { const stringified = JSON.stringify(verificationValue); @@ -2258,15 +2323,17 @@ export class Verifications extends Logger { // create the signature for the verification const signedSignature = await this.options.executor.web3.eth.accounts.sign( this.options.nameResolver.soliditySha3( - targetIdentity, uint256VerificationName, verificationData).replace('0x', ''), - '0x' + await this.options.accountStore.getPrivateKey(issuer) + targetIdentity, uint256VerificationName, verificationData, + ).replace('0x', ''), + `0x${await this.options.accountStore.getPrivateKey(issuer)}`, ); // build description hash if required let ensFullNodeHash; if (descriptionDomain) { ensFullNodeHash = this.options.nameResolver.namehash( - this.getFullDescriptionDomainWithHash(topic, descriptionDomain)); + this.getFullDescriptionDomainWithHash(topic, descriptionDomain), + ); } // return arguments for setting verification @@ -2279,7 +2346,7 @@ export class Verifications extends Logger { verificationData, verificationDataUrl, ensFullNodeHash: ensFullNodeHash || nullBytes32, - } + }; } /** @@ -2292,9 +2359,9 @@ export class Verifications extends Logger { private async getSubjectType(subject: string, isIdentity?: boolean): Promise { if (subject.length === 66) { return 'contract'; - } else if (isIdentity && subject.length === 42) { + } if (isIdentity && subject.length === 42) { return 'account'; - } else if (!this.subjectTypes[subject]) { + } if (!this.subjectTypes[subject]) { // fills subject type upon retrieval await this.getIdentityForAccount(subject); } @@ -2312,6 +2379,7 @@ export class Verifications extends Logger { */ private async signPackedHash(accountId: string, toSign: any): Promise { return this.options.executor.signer.signMessage( - accountId, this.options.nameResolver.soliditySha3(...toSign)); + accountId, this.options.nameResolver.soliditySha3(...toSign), + ); } } diff --git a/src/votings/votings.spec.ts b/src/votings/votings.spec.ts index a8b73885..2be2693f 100644 --- a/src/votings/votings.spec.ts +++ b/src/votings/votings.spec.ts @@ -18,8 +18,8 @@ */ import 'mocha'; -import chaiAsPromised = require('chai-as-promised'); -import { ContractLoader, Executor } from '@evan.network/dbcp'; +import * as chaiAsPromised from 'chai-as-promised'; +import { Executor } from '@evan.network/dbcp'; import { expect, use } from 'chai'; import { accounts } from '../test/accounts'; @@ -27,35 +27,36 @@ import { TestUtils } from '../test/test-utils'; import { Votings } from './votings'; function timeout(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } -const [ votingOwner, member, nonMember ] = accounts; +const [votingOwner, member, nonMember] = accounts; use(chaiAsPromised); -describe('Voting handler', function() { +describe('Voting handler', function test() { this.timeout(600000); - let contractLoader: ContractLoader; let executor: Executor; let votingContract: any; let votings: Votings; let web3: any; - let createDescription = () => `${Math.random()} is the most awesome random number ever`; - let createProposal = async (description) => - votings.createProposal(votingContract, votingOwner, { description, }); - let nextBlock = async () => await executor.executeSend({ from: votingOwner, value: 0, to: votingOwner }); - let psleep = (ms) => new Promise(s => setTimeout(() => s(), ms)); + const createDescription = () => `${Math.random()} is the most awesome random number ever`; + const createProposal = async (description) => votings.createProposal( + votingContract, votingOwner, { description }, + ); + const nextBlock = async () => executor.executeSend( + { from: votingOwner, value: 0, to: votingOwner }, + ); + const psleep = (ms) => new Promise((s) => setTimeout(() => s(), ms)); before(async () => { web3 = TestUtils.getWeb3(); - contractLoader = await TestUtils.getContractLoader(web3); executor = await TestUtils.getExecutor(web3); votings = await TestUtils.getVotings(web3); }); - it('can create a new voting contract', async() => { + it('can create a new voting contract', async () => { votingContract = await votings.createContract( votingOwner, { @@ -160,15 +161,15 @@ describe('Voting handler', function() { await expect(votings.execute(votingContract, votingOwner, proposal)).to.be.rejected; }); - it('can perform transactions (and not only votes) via congress contract', async() => { + it('can perform transactions (and not only votes) via congress contract', async () => { const testContract = await executor.createContract( - 'TestContract', ['abc'], { from: votingOwner, gas: 2000000 }); + 'TestContract', ['abc'], { from: votingOwner, gas: 2000000 }, + ); // input for: setData("def") - const setDataToDef = '0x47064d6a' + - '0000000000000000000000000000000000000000000000000000000000000020' + - '0000000000000000000000000000000000000000000000000000000000000003' + - '6465660000000000000000000000000000000000000000000000000000000000' - ; + const setDataToDef = '0x47064d6a' + + '0000000000000000000000000000000000000000000000000000000000000020' + + '0000000000000000000000000000000000000000000000000000000000000003' + + '6465660000000000000000000000000000000000000000000000000000000000'; const proposal = await votings.createProposal( votingContract, votingOwner, @@ -176,7 +177,7 @@ describe('Voting handler', function() { description: createDescription(), data: setDataToDef, to: testContract.options.address, - } + }, ); await timeout(5000); await votings.vote(votingContract, votingOwner, proposal, true); @@ -189,7 +190,8 @@ describe('Voting handler', function() { // make new block to update time check in contract (for gas estimation) await nextBlock(); await timeout(5000); - await expect(votings.execute(votingContract, votingOwner, proposal, setDataToDef)).not.to.be.rejected; + await expect(votings.execute(votingContract, votingOwner, proposal, setDataToDef)) + .not.to.be.rejected; expect(await executor.executeContractCall(testContract, 'data')).to.eq('def'); }); }); @@ -206,7 +208,8 @@ describe('Voting handler', function() { ); await votings.addMember(contract, votingOwner, member, { name: 'Member No. 2' }); const proposal = await votings.createProposal( - contract, votingOwner, { description: createDescription() }); + contract, votingOwner, { description: createDescription() }, + ); await votings.vote(contract, votingOwner, proposal, true); await votings.vote(contract, member, proposal, true); @@ -228,7 +231,8 @@ describe('Voting handler', function() { ); await votings.addMember(contract, votingOwner, member, { name: 'Member No. 2' }); const proposal = await votings.createProposal( - contract, votingOwner, { description: createDescription() }); + contract, votingOwner, { description: createDescription() }, + ); await votings.vote(contract, votingOwner, proposal, true); await votings.vote(contract, member, proposal, true); @@ -240,7 +244,7 @@ describe('Voting handler', function() { }); }); - describe('when paging through proposals', async() => { + describe('when paging through proposals', async () => { const descriptions = [ '0.17664580626145200 is the most awesome random number ever', '0.67163320670743580 is the most awesome random number ever', @@ -284,28 +288,27 @@ describe('Voting handler', function() { marginOfVotesForMajority: 0, }, ); - for (let description of descriptions) { + for (const description of descriptions) { await votings.createProposal(contract, votingOwner, { description }); } - // contract = contractLoader.loadContract('Congress', '0x9C1F4d7E75163D054A98700b1568347C5f687238'); }); it('can retrieve a single page', async () => { const retrieved = await votings.getProposalInfos(contract); expect(retrieved.totalCount).to.eq(numOfProposals); expect(retrieved.results.length).to.eq(Math.min(10, numOfProposals)); - for (let [i, result] of retrieved.results.entries()) { + for (const [i, result] of retrieved.results.entries()) { expect(result.description).to.eq(descriptionsNewestFirst[i]); } }); it('can page to last result', async () => { - let count = await votings.getProposalCount(contract); - let results = await votings.getProposalInfos(contract, count); + const count = await votings.getProposalCount(contract); + const results = await votings.getProposalInfos(contract, count); expect(results.totalCount).to.eq(numOfProposals); expect(results.results.length).to.eq(Math.min(numOfProposals, results.totalCount)); - for (let [i, result] of results.results.entries()) { + for (const [i, result] of results.results.entries()) { expect(result.description).to.eq(descriptionsNewestFirst[i]); } }); diff --git a/src/votings/votings.ts b/src/votings/votings.ts index 4b575af5..0d8c2bb7 100644 --- a/src/votings/votings.ts +++ b/src/votings/votings.ts @@ -28,7 +28,6 @@ import { NameResolver } from '../name-resolver'; const nullAddress = '0x0000000000000000000000000000000000000000'; -const nullBytes32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; const defaultProposalOptions = { data: '0x', to: nullAddress, @@ -46,11 +45,11 @@ export interface MemberInfo { /** * description text of member */ - name: string; - /** - * date of joining votings contract - */ - memberSince: string; + name: string; + /** + * date of joining votings contract + */ + memberSince: string; } /** @@ -112,11 +111,11 @@ export interface ProposalInfos { /** * proposals of current page (length is 10) */ - results: ProposalInfo[], + results: ProposalInfo[]; /** * total number of results */ - totalCount: number, + totalCount: number; } /** @@ -177,12 +176,12 @@ export interface VotingsOptions extends LoggerOptions { * @class Votings (name) */ export class Votings extends Logger { - options: VotingsOptions; + public options: VotingsOptions; /** * create new Votings instance. */ - constructor(options: VotingsOptions) { + public constructor(options: VotingsOptions) { super(options); this.options = options; } @@ -197,11 +196,11 @@ export class Votings extends Logger { * @return {Promise} resolved when done */ public async addMember( - contract: string|any, - accountId: string, - targetAccount: string, - memberOptions: MemberOptions, - ): Promise { + contract: string|any, + accountId: string, + targetAccount: string, + memberOptions: MemberOptions, + ): Promise { await this.options.executor.executeContractTransaction( this.ensureContract(contract), 'addMember', @@ -220,14 +219,17 @@ export class Votings extends Logger { * @return {Promise} votings contract web3 instance */ public async createContract( - accountId: string, votingsContractOptions: VotingsContractOptions): Promise { + accountId: string, + votingsContractOptions: VotingsContractOptions, + ): Promise { const congressOptions = [ votingsContractOptions.minimumQuorumForProposals, votingsContractOptions.minutesForDebate, votingsContractOptions.marginOfVotesForMajority, ]; - return await this.options.executor.createContract( - 'Congress', congressOptions, { from: accountId, gas: 2000000 }); + return this.options.executor.createContract( + 'Congress', congressOptions, { from: accountId, gas: 2000000 }, + ); } /** @@ -240,11 +242,14 @@ export class Votings extends Logger { * @return {Promise} id of new proposal */ public async createProposal( - contract: string|any, accountId: string, proposalOptions: ProposalOptions): Promise { - this.log(`creating proposal in congress "${contract.options.address}" ` + - `with account ${accountId}`, 'info'); - const options = Object.assign({}, defaultProposalOptions, proposalOptions); - return await this.options.executor.executeContractTransaction( + contract: string|any, + accountId: string, + proposalOptions: ProposalOptions, + ): Promise { + this.log(`creating proposal in congress "${contract.options.address}" ` + + `with account ${accountId}`, 'info'); + const options = { ...defaultProposalOptions, ...proposalOptions }; + return this.options.executor.executeContractTransaction( this.ensureContract(contract), 'newProposal', { @@ -271,9 +276,12 @@ export class Votings extends Logger { * @return {Promise} resolved when done */ public async execute( - contract: string|any, accountId: string, proposal: string|number, data = '0x'): Promise { - this.log(`executing proposal in congress "${contract.options.address}", ` + - `proposal "${proposal}" with account ${accountId}`, 'info'); + contract: string|any, + accountId: string, + proposal: string|number, data = '0x', + ): Promise { + this.log(`executing proposal in congress "${contract.options.address}", ` + + `proposal "${proposal}" with account ${accountId}`, 'info'); await this.options.executor.executeContractTransaction( this.ensureContract(contract), 'executeProposal', @@ -345,7 +353,7 @@ export class Votings extends Logger { description: fromContract.description, executed: fromContract.executed, minExecutionDate: parseInt(`${fromContract.minExecutionDate}000`, 10), - numberOfVotes: parseInt(fromContract.numberOfVotes, 10), + numberOfVotes: parseInt(fromContract.numberOfVotes, 10), proposalHash: fromContract.proposalHash, proposalPassed: fromContract.proposalPassed, to: fromContract.recipient, @@ -364,17 +372,19 @@ export class Votings extends Logger { * @return {Promise} proposals listing */ public async getProposalInfos( - contract: string|any, - count = 10, - offset = 0, - reverse = true): Promise { + contract: string|any, + count = 10, + offset = 0, + reverse = true, + ): Promise { let totalCountString; const votingsContract = this.ensureContract(contract); const results = await this.options.nameResolver.getArrayFromUintMapping( votingsContract, async () => { totalCountString = await this.options.executor.executeContractCall( - votingsContract, 'numProposals'); + votingsContract, 'numProposals', + ); return totalCountString; }, (i) => this.getProposalInfo(votingsContract, i), @@ -383,7 +393,7 @@ export class Votings extends Logger { reverse, ); return { - results: results.filter(result => !!result.minExecutionDate), + results: results.filter((result) => !!result.minExecutionDate), totalCount: parseInt(totalCountString, 10), }; } @@ -391,8 +401,8 @@ export class Votings extends Logger { /** * checks if a given account is member in voting contract * - * @param {string|any} contract web3 voting contract instance or contract address - * @param {string} targetAccount account to check + * @param {string|any} contract web3 voting contract instance or contract address + * @param {string} targetAccount account to check * @return {Promise} true if member, false otherwise. */ public async isMember(contract: string|any, targetAccount: string): Promise { @@ -409,10 +419,10 @@ export class Votings extends Logger { * @return {Promise} resolved when done */ public async removeMember( - contract: string|any, - accountId: string, - targetAccount: string, - ): Promise { + contract: string|any, + accountId: string, + targetAccount: string, + ): Promise { await this.options.executor.executeContractTransaction( this.ensureContract(contract), 'removeMember', @@ -432,9 +442,14 @@ export class Votings extends Logger { * @return {Promise} resolved when done */ public async vote( - contract: string|any, accountId: string, proposal: string, accept: boolean, comment = ''): Promise { - this.log(`voting for proposal in congress "${contract.options.address}", ` + - `proposal "${proposal}" with account ${accountId}, responst is "${accept}"`, 'info'); + contract: string|any, + accountId: string, + proposal: string, + accept: boolean, + comment = '', + ): Promise { + this.log(`voting for proposal in congress "${contract.options.address}", ` + + `proposal "${proposal}" with account ${accountId}, responst is "${accept}"`, 'info'); await this.options.executor.executeContractTransaction( this.ensureContract(contract), 'vote', @@ -452,9 +467,8 @@ export class Votings extends Logger { * @return {any} contract instance */ private ensureContract(contract: string|any): any { - return typeof contract === 'string' ? - this.options.contractLoader.loadContract('Congress', contract) : - contract - ; + return typeof contract === 'string' + ? this.options.contractLoader.loadContract('Congress', contract) + : contract; } } diff --git a/tsconfig-test.json b/tsconfig-test.json new file mode 100644 index 00000000..166caf96 --- /dev/null +++ b/tsconfig-test.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "declaration": true, + "inlineSourceMap": true, + "inlineSources": true, + "lib": [ + "es2017" + ], + "module": "commonjs", + "outDir": "./dist-test", + "paths": { + "bcc": [ + "./src/dist/index.js" + ] + }, + "resolveJsonModule": true, + "rootDir": "./src", + "target": "es2017" + }, + "exclude": [ + "bundles", + "dist", + "docs", + "libs", + "node_modules", + "scripts" + ], + "include": [ + "src/**/*.ts" + ] +} diff --git a/tsconfig.json b/tsconfig.json index 1e9e24de..58e3cdef 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -30,4 +30,4 @@ "include": [ "src/**/*.ts" ] -} \ No newline at end of file +} diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 6370c9e0..00000000 --- a/tslint.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "rulesDirectory": [ - "tslint-no-unused-expression-chai" - ], - "rules": { - "class-name": true, - "comment-format": [ - true, - "check-space" - ], - "curly": true, - "eofline": true, - "forin": true, - "indent": [ - true, - "spaces" - ], - "label-position": true, - "max-line-length": [ - true, - 140 - ], - "member-access": false, - "member-ordering": [ - true, - "static-before-instance", - "variables-before-functions" - ], - "no-arg": true, - "no-bitwise": true, - "no-console": [ - true, - "debug", - "info", - "time", - "timeEnd", - "trace" - ], - "no-construct": true, - "no-debugger": true, - "no-duplicate-variable": true, - "no-empty": false, - "no-eval": true, - "no-inferrable-types": true, - "no-shadowed-variable": true, - "no-string-literal": false, - "no-switch-case-fall-through": true, - "no-trailing-whitespace": true, - "no-unused-expression-chai": true, - "no-use-before-declare": true, - "no-var-keyword": true, - "object-literal-sort-keys": false, - "one-line": [ - true, - "check-open-brace", - "check-catch", - "check-else", - "check-whitespace" - ], - "quotemark": [ - true, - "single" - ], - "radix": true, - "semicolon": [ - "always" - ], - "triple-equals": [ - true, - "allow-null-check" - ], - "typedef-whitespace": [ - true, - { - "call-signature": "nospace", - "index-signature": "nospace", - "parameter": "nospace", - "property-declaration": "nospace", - "variable-declaration": "nospace" - } - ], - "variable-name": false, - "whitespace": [ - true, - "check-branch", - "check-decl", - "check-operator", - "check-separator", - "check-type" - ] - } -}