[
  {
    "path": ".bumpversion.cfg",
    "content": "[bumpversion]\ncurrent_version = 3.1.2\ncommit = True\ntag = True\n\n[bumpversion:file:setup.py]\nsearch = version='{current_version}'\nreplace = version='{new_version}'\n\n[bumpversion:file:ocean_lib/__init__.py]\nsearch = __version__ = '{current_version}'\nreplace = __version__ = '{new_version}'\n"
  },
  {
    "path": ".codeclimate.yml",
    "content": "##\n## Copyright 2023 Ocean Protocol Foundation\n## SPDX-License-Identifier: Apache-2.0\n##\nversion: \"2\"\nchecks:\n  argument-count:\n    enabled: false\n  file-lines:\n    enabled: false\n  complex-logic:\n    enabled: false\n  method-complexity:\n    enabled: false\n  method-count:\n    enabled: false\n  method-lines:\n    enabled: false"
  },
  {
    "path": ".copyright.tmpl",
    "content": "Copyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n"
  },
  {
    "path": ".coveragerc",
    "content": "[run]\nomit = *test*\n"
  },
  {
    "path": ".docignore",
    "content": "**test**\nCHANGELOG.md"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "##\n## Copyright 2023 Ocean Protocol Foundation\n## SPDX-License-Identifier: Apache-2.0\n##\n\n# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n- package-ecosystem: pip\n  directory: \"/\"\n  schedule:\n    interval: weekly\n    time: '03:00'\n    timezone: Europe/Berlin\n"
  },
  {
    "path": ".github/workflows/black.yml",
    "content": "##\n## Copyright 2023 Ocean Protocol Foundation\n## SPDX-License-Identifier: Apache-2.0\n##\nname: black\n\non: [push]\n\njobs:\n  black:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: psf/black@stable\n        with:\n          options: \"--check --verbose\"\n          version: \"22.10.0\"\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "##\n## Copyright 2023 Ocean Protocol Foundation\n## SPDX-License-Identifier: Apache-2.0\n##\nname: Ocean.py multiple OS\n\non:\n  push:\n    branches:\n      - main\n    tags:\n      - '**'\n  pull_request:\n    branches:\n      - '**'\n\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, macos-latest, windows-latest]\n        version: ['3.8', '3.10', '3.11']\n    steps:\n      - name: Setup Ocean.py\n        uses: actions/checkout@v3\n      - name: Set up Python ${{ matrix.version }}\n        uses: actions/setup-python@v4\n        with:\n          python-version: ${{ matrix.version }}\n      - name: Install pypa/build\n        run: >-\n          python -m\n          pip install\n          build\n          --user\n      - name: Build a binary wheel and a source tarball\n        run: >-\n          python -m\n          build\n          --sdist\n          --wheel\n          --outdir dist/\n      - name: Install Mac OS specific dependencies\n        if: ${{ matrix.os == 'macos-latest' }}\n        run: |\n          brew install autoconf automake libtool pkg-config\n      - name: Install dependencies\n        working-directory: ${{ github.workspace }}\n        # vyper is grounded here until it declares explicit support for Python 3.11\n        run: |\n          python -m pip install --upgrade pip\n          pip install vyper==0.3.7 --ignore-requires-python\n          pip install -r requirements_dev.txt\n"
  },
  {
    "path": ".github/workflows/libcheck.yml",
    "content": "##\n## Copyright 2023 Ocean Protocol Foundation\n## SPDX-License-Identifier: Apache-2.0\n##\nname: Ocean.py library check\n\non:\n  schedule:\n    - cron: '30 5 * * 2'\n  workflow_dispatch:\n\njobs:\n  build:\n    environment: CC_REPORTER_ID\n    runs-on: ubuntu-latest\n    steps:\n      - name: Setup Ocean.py\n        uses: actions/checkout@v2\n      - name: Set up Python 3.8\n        uses: actions/setup-python@v2\n        with:\n          python-version: '3.8'\n      - uses: actions/checkout@v2\n        name: Checkout Barge\n        with:\n          repository: \"oceanprotocol/barge\"\n          path: 'barge'\n      - name: Run Barge\n        working-directory: ${{ github.workspace }}/barge\n        env:\n          GANACHE_FORK: london\n        run: |\n          bash -x start_ocean.sh --no-dashboard 2>&1 --with-provider2 --with-c2d > start_ocean.log &\n          for i in $(seq 1 108); do\n            sleep 5\n            [ -f \"$HOME/.ocean/ocean-contracts/artifacts/ready\" -a -f \"$HOME/.ocean/ocean-c2d/ready\" ] && break\n            done\n          ls -la \"$HOME/.ocean/ocean-contracts/artifacts/\"\n      - name: Install dependencies\n        working-directory: ${{ github.workspace }}\n        run: |\n          python -m pip install --upgrade pip\n          pip install ocean-lib\n          pip install mkcodes pytest matplotlib\n      - name: Delete default runner images\n        run: |\n          docker image rm node:14\n          docker image rm node:14-alpine\n          docker image rm node:16\n          docker image rm node:16-alpine\n          docker image rm node:18\n          docker image rm node:18-alpine\n          docker image rm buildpack-deps:buster\n          docker image rm buildpack-deps:bullseye\n          docker image rm debian:10\n          docker image rm debian:11\n          docker image rm moby/buildkit:latest\n      - name: Generate and test readmes\n        working-directory: ${{ github.workspace }}\n        env:\n          TEST_PRIVATE_KEY1: \"0x8467415bb2ba7c91084d932276214b11a3dd9bdb2930fefa194b666dd8020b99\"\n          TEST_PRIVATE_KEY2: \"0x1d751ded5a32226054cd2e71261039b65afb9ee1c746d055dd699b1150a5befc\"\n          TEST_PRIVATE_KEY3: \"0x732fbb7c355aa8898f4cff92fa7a6a947339eaf026a08a51f171199e35a18ae0\"\n          ADDRESS_FILE: \"~/.ocean/ocean-contracts/artifacts/address.json\"\n          OCEAN_NETWORK_URL: \"http://127.0.0.1:8545\"\n          OCEAN_CONFIG_FILE: \"config.ini\"\n          FACTORY_DEPLOYER_PRIVATE_KEY: \"0xc594c6e5def4bab63ac29eed19a134c130388f74f019bc74b8f4389df2837a58\"\n          MUMBAI_RPC_URL: ${{ secrets.MUMBAI_RPC_URL }}\n        run: |\n          mkcodes --github --output tests/generated-readmes/test_{name}.{ext} READMEs\n          pytest tests/readmes/test_readmes.py\n      - name: Slack notify via webhook\n        uses: up9cloud/action-notify@master\n        if: cancelled() == false\n        env:\n          GITHUB_JOB_STATUS: ${{ job.status }}\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}\n"
  },
  {
    "path": ".github/workflows/pytest.yml",
    "content": "##\n## Copyright 2023 Ocean Protocol Foundation\n## SPDX-License-Identifier: Apache-2.0\n##\nname: Ocean.py tests\n\non:\n  - push\n  - pull_request\n\nenv:\n  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\njobs:\n  build:\n    environment: CC_REPORTER_ID\n    runs-on: ubuntu-latest\n    steps:\n      - name: Setup Ocean.py\n        uses: actions/checkout@v2\n      - name: Set up Python 3.8\n        uses: actions/setup-python@v2\n        with:\n          python-version: '3.8'\n      - uses: actions/checkout@v2\n        name: Checkout Barge\n        with:\n          repository: \"oceanprotocol/barge\"\n          path: 'barge'\n      - name: Run Barge\n        working-directory: ${{ github.workspace }}/barge\n        env:\n          GANACHE_FORK: london\n        run: |\n          bash -x start_ocean.sh --no-dashboard 2>&1 --with-provider2 --with-thegraph --with-c2d --skip-subgraph-deploy > start_ocean.log &\n      - name: Install dependencies\n        working-directory: ${{ github.workspace }}\n        run: |\n          python -m pip install --upgrade pip\n          pip install -r requirements_dev.txt\n      - name: Delete default runner images\n        run: |\n          docker image rm node:16\n          docker image rm node:16-alpine\n          docker image rm node:18\n          docker image rm node:18-alpine\n          docker image rm debian:10\n          docker image rm debian:11\n          docker image rm moby/buildkit:latest\n      - name: Wait for contracts deployment\n        working-directory: ${{ github.workspace }}/barge\n        run: |\n          for i in $(seq 1 250); do\n            sleep 5\n            [ -f \"$HOME/.ocean/ocean-contracts/artifacts/ready\" -a -f \"$HOME/.ocean/ocean-c2d/ready\" ] && break\n            done\n      - name: \"Read address.json contents\"\n        working-directory: ${{ github.workspace }}\n        run: cat \"$HOME/.ocean/ocean-contracts/artifacts/address.json\"\n      - name: Test with pytest\n        run: |\n          mkcodes --github --output tests/generated-readmes/test_{name}.{ext} READMEs\n          coverage run --source ocean_lib -m pytest\n          coverage report\n          coverage xml\n        env:\n          REMOTE_TEST_PRIVATE_KEY1: ${{secrets.REMOTE_TEST_PRIVATE_KEY1}}\n          REMOTE_TEST_PRIVATE_KEY2: ${{secrets.REMOTE_TEST_PRIVATE_KEY2}}\n          MUMBAI_RPC_URL: ${{secrets.MUMBAI_RPC_URL}}\n      - name:  docker logs\n        run: docker logs ocean_aquarius_1 && docker logs ocean_provider_1\n        if: ${{ failure() }}\n      - name: Publish code coverage\n        uses: paambaati/codeclimate-action@v2.7.5\n        env:\n          CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "##\n## Copyright 2023 Ocean Protocol Foundation\n## SPDX-License-Identifier: Apache-2.0\n##\nname: Ocean.py release\n\non:\n  release:\n    types: [created]\n\njobs:\n  build-n-publish:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Setup Ocean.py\n        uses: actions/checkout@v2\n      - name: Set up Python 3.8\n        uses: actions/setup-python@v2\n        with:\n          python-version: '3.8'\n      - name: Install pypa/build\n        run: >-\n          python -m\n          pip install\n          build\n          --user\n      - name: Build a binary wheel and a source tarball\n        run: >-\n          python -m\n          build\n          --sdist\n          --wheel\n          --outdir dist/\n      - name: Publish package\n        uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          user: __token__\n          verbose: true\n          password: ${{ secrets.PYPI_PASSWORD }}\n\n"
  },
  {
    "path": ".gitignore",
    "content": ".history\n__pycache__\n.pytest_cache/\n*.pyc\n*~\n.eggs/\nartifacts/address.json\n/venv/\nocean_lib.egg-info/\n/build/\n/contracts/\n/interfaces/\n/reports/\n/tests/generated-readmes/test*\n\n.coverage\n.tox/\nconsume-downloads/\ncoverage.xml\nsetup-local.sh\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "##\n## Copyright 2023 Ocean Protocol Foundation\n## SPDX-License-Identifier: Apache-2.0\n##\nrepos:\n-   repo: https://github.com/PyCQA/isort\n    rev: 5.12.0\n    hooks:\n    - id: isort\n-   repo: https://github.com/psf/black\n    rev: 'refs/tags/22.6.0:refs/tags/22.6.0'\n    hooks:\n    -   id: black\n-   repo: https://github.com/pycqa/flake8\n    rev: 6.0.0\n    hooks:\n    - id: flake8\n-   repo: https://github.com/johann-petrak/licenseheaders.git\n    rev: v0.8.8\n    hooks:\n    - id: licenseheaders\n      args: [\"-t\", \".copyright.tmpl\", \"-f\"]\n"
  },
  {
    "path": ".prospector.yml",
    "content": "##\n## Copyright 2023 Ocean Protocol Foundation\n## SPDX-License-Identifier: Apache-2.0\n##\npep257:\n  disable:\n    - D213\n\npylint:\n  run: false\n\nfrosted:\n  run: false\n\nmypy:\n  run: false\n\npyroma:\n  run: false\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\nSee [`releases`](https://github.com/oceanprotocol/ocean.py/releases).\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "<!--\nCopyright 2025 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n> ocean.py: a Python library to privately & securely publish, exchange, and consume data.\n\n⚠ Note: as of early 2025, this codebase is not being maintained. It might work, it might not. If you find a bug, feel free to report it, but do not expect it to be fixed. \n\nOcean.py helps data scientists earn $ from their AI models, track provenance of data & compute, and get more data. (More details [here](https://docs.oceanprotocol.com/data-science).)\n\nOcean.py makes these tasks easy:\n\n- **Publish** data services: data feeds, REST APIs, downloadable files or compute-to-data. Create an ERC721 **data NFT** for each service, and ERC20 **datatoken** for access (1.0 datatokens to access).\n- **Sell** datatokens via for a fixed price. Sell data NFTs.\n- **Transfer** data NFTs & datatokens to another owner, and **all other ERC721 & ERC20 actions** using [web3.py](https://web3py.readthedocs.io).\n\nocean.py is part of the [Ocean Protocol](https://www.oceanprotocol.com) toolset.\n\nThis is in beta state. If you run into problems, please open up a [new issue](/issues).\n\n## 🏄 Quickstart\n\nFollow these steps in sequence to ramp into Ocean.\n\n 1. **[Install Ocean](READMEs/install.md)**\n 2. **Setup:**\n    - **[Remote](READMEs/setup-remote.md)** (Win, MacOS, Linux)\n    - *or* **[Local](READMEs/setup-local.md)** (Linux only)\n 3. **[Walk through main flow](READMEs/main-flow.md)**: publish asset, post for free / for sale, dispense it / buy it, and consume it\n\n### Tools\n\n- [Define gas strategy](READMEs/gas-strategy-remote.md) - auto-determine gas fee for remote networks\n- [Search & filter data](READMEs/search-and-filter-assets.md) - find assets by tag\n- [Custody-light flow](READMEs/custody-light-flow.md) - consume a free & a priced asset without custody\n\n### Use-case flows\n\n- [Challenge DF](https://github.com/oceanprotocol/predict-eth) - prize $$ to predict future ETH price\n- [Data Farming](READMEs/df.md) - curate data assets, earn rewards\n\n### On-chain key-value store via data NFTs\n\n- [Sharing public data on-chain](READMEs/key-value-public.md) - e.g. public AI models\n- [Sharing private data on-chain](READMEs/key-value-private.md) - e.g. private AI models\n\n### More types of data assets\n\nEach of the following shows how to publish & consume a particular type of data.\n- [C2D](READMEs/c2d-flow.md) - tokenize & monetize AI algorithms via Compute-to-Data\n- [REST API](READMEs/publish-flow-restapi.md) - Example on Binance ETH price feed\n- [GraphQL](READMEs/publish-flow-graphql.md) - Example on Ocean Data NFTs\n- [On-chain data](READMEs/publish-flow-onchain.md) - Example on Ocean swap fees\n- [Adding credentials](READMEs/publish-flow-credentials.md) - Example on publishing an asset with custom credentials\n\n### Learn more\n- [Understand config parameters](READMEs/parameters.md) - envvars vs files\n- [Learn about off-chain services](READMEs/services.md) - Ocean Provider for data services, Aquarius metadata store\n\n## 🦑 Development\n\n- **[Developers flow](READMEs/developers.md)** - to further develop ocean.py\n- [Release process](READMEs/release-process.md) - to do a new release of ocean.py\n\n## 🏛 License\n\n    Copyright ((C)) 2025 Ocean Protocol Foundation\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n"
  },
  {
    "path": "READMEs/c2d-flow-more-examples.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Compute-to-Data (C2D) Flow - More Examples\n\nPlease note that these code snippets are not automatically tested, so they might be out of date. Always refer to the main\n[c2d-flow README](https://github.com/oceanprotocol/ocean.py/blob/v4main/READMEs/c2d-flow.md) for the latest fully tested c2d flow.\n\n## Example 1: Image Processing\n\nRun the [c2d-flow README](https://github.com/oceanprotocol/ocean.py/blob/v4main/READMEs/c2d-flow.md)\nwith the following alterations:\n\n### 3. Alice publishes a dataset\n\nIn Step #3 where Alice publishes a dataset, use [peppers.tiff](https://sipi.usc.edu/database/database.php?volume=misc&image=13#top):\n\n```python\n# Specify metadata, using the peppers.tiff image\nDATA_date_created = \"2021-12-28T10:55:11Z\"\nDATA_metadata = {\n    \"created\": DATA_date_created,\n    \"updated\": DATA_date_created,\n    \"description\": \"peppers image\",\n    \"name\": \"peppers\",\n    \"type\": \"dataset\",\n    \"author\": \"Trent\",\n    \"license\": \"CC0: PublicDomain\",\n}\n\n# ocean.py offers multiple file types, but a simple url file should be enough for this example\nfrom ocean_lib.structures.file_objects import UrlFile\nDATA_url_file = UrlFile(\n    url=\"https://raw.githubusercontent.com/oceanprotocol/c2d-examples/main/peppers_and_grayscale/peppers.tiff\"\n)\n```\n\n### 4. Alice publishes an algorithm\n\nIn step #4 where Alice publishes an algorithm, use a standard grayscale algorithm:\n\n```python\n# Specify metadata, using the grayscale algorithm\nALGO_date_created = \"2021-12-28T10:55:11Z\"\nALGO_metadata = {\n    \"created\": ALGO_date_created,\n    \"updated\": ALGO_date_created,\n    \"description\": \"grayscale\",\n    \"name\": \"grayscale\",\n    \"type\": \"algorithm\",\n    \"author\": \"Trent\",\n    \"license\": \"CC0: PublicDomain\",\n    \"algorithm\": {\n        \"language\": \"python\",\n        \"format\": \"docker-image\",\n        \"version\": \"0.1\",\n        \"container\": {\n            \"entrypoint\": \"python $ALGO\",\n            \"image\": \"oceanprotocol/algo_dockers\",\n            \"tag\": \"python-branin\",  # This image provides all the dependencies of the grayscale.py algorithm\n            \"checksum\": \"sha256:8221d20c1c16491d7d56b9657ea09082c0ee4a8ab1a6621fa720da58b09580e4\",\n        },\n    }\n}\n\n# ocean.py offers multiple file types, but a simple url file should be enough for this example\nfrom ocean_lib.structures.file_objects import UrlFile\nALGO_url_file = UrlFile(\n    url=\"https://raw.githubusercontent.com/oceanprotocol/c2d-examples/main/peppers_and_grayscale/grayscale.py\"\n)\n```\n\n### Display and Save the Result\n\nInstall dependencies:\n\n`pip install Pillow`\n\nDisplay the image:\n\n```python\nfrom PIL import Image\nimport io\n\nimage = Image.open(io.BytesIO(result))\nimage.show()\n```\n\nSave the image:\n\n```python\nimage.save('grayscale.png')\n```\n\n## Example 2: Logistic Regression for Classification\n\nRun the [c2d-flow README](https://github.com/oceanprotocol/ocean.py/blob/v4main/READMEs/c2d-flow.md)\nwith the following alterations:\n\n### 3. Alice publishes a dataset\n\nIn Step #3 where Alice publishes a dataset, use the [Iris Flower Dataset](https://en.wikipedia.org/wiki/Iris_flower_data_set):\n\n```python\n# Specify metadata, using the iris dataset\nDATA_date_created = \"2019-12-28T10:55:11Z\"\nDATA_metadata = {\n    \"created\": DATA_date_created,\n    \"updated\": DATA_date_created,\n    \"description\": \"The Iris flower dataset is a multivariate dataset to train classification algorithms\",\n    \"name\": \"Iris Flower Dataset\",\n    \"type\": \"dataset\",\n    \"author\": \"Ocean Protocol & Raven Protocol\",\n    \"license\": \"MIT\",\n}\n\n# ocean.py offers multiple file types, but a simple url file should be enough for this example\nfrom ocean_lib.structures.file_objects import UrlFile\nDATA_url_file = UrlFile(\n    url=\"https://raw.githubusercontent.com/oceanprotocol/c2d-examples/main/iris_and_logisitc_regression/dataset_61_iris.csv\"\n)\n```\n\n### 4. Alice publishes an algorithm\n\nIn step #4 where Alice publishes an algorithm, use a standard grayscale algorithm:\n\n```python\n# Specify metadata, using the Logistic Regression algorithm\nALGO_date_created = \"2020-01-28T10:55:11Z\"\nALGO_metadata = {\n    \"created\": ALGO_date_created,\n    \"updated\": ALGO_date_created,\n    \"description\": \"Logistic Regression\",\n    \"name\": \"Logistic Regression\",\n    \"type\": \"algorithm\",\n    \"author\": \"Ocean Protocol & Raven Protocol\",\n    \"license\": \"MIT\",\n    \"algorithm\": {\n        \"language\": \"python\",\n        \"format\": \"docker-image\",\n        \"version\": \"0.1\",\n        \"container\": {\n            \"entrypoint\": \"python $ALGO\",\n            \"image\": \"oceanprotocol/algo_dockers\",\n            \"tag\": \"python-panda\", # This image provides all the dependencies of the logistic_regression.py algorithm\n            \"checksum\": \"sha256:7fc268f502935d11ff50c54e3776dda76477648d5d83c2e3c4fdab744390ecf2\",\n        },\n    }\n}\n\n# ocean.py offers multiple file types, but a simple url file should be enough for this example\nfrom ocean_lib.structures.file_objects import UrlFile\nALGO_url_file = UrlFile(\n    url=\"https://raw.githubusercontent.com/oceanprotocol/c2d-examples/main/iris_and_logisitc_regression/logistic_regression.py\"\n)\n```\n\n### Display the Result\n\nInstall dependencies:\n\n`pip install numpy, matplotlib`\n\nDisplay the result:\n\n```python\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nh = 0.02  # step size in the mesh\nxx, yy = np.meshgrid(np.arange(3.8, 8.4, h), np.arange(1.5, 4.9, h))\n\nplt.figure(1, figsize=(4, 3))\nplt.pcolormesh(xx, yy, model, cmap=plt.cm.Paired)\n\nplt.xlabel(\"Sepal length\")\nplt.ylabel(\"Sepal width\")\n\nplt.xlim(xx.min(), xx.max())\nplt.ylim(yy.min(), yy.max())\n\nplt.show()\n```\n"
  },
  {
    "path": "READMEs/c2d-flow.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Quickstart: Compute-to-Data (C2D) Flow\n\nThis quickstart describes a C2D flow, using a remote setup on Mumbai testnet.\n\nHere are the steps:\n\n1. Setup\n2. Alice publishes dataset\n3. Alice publishes algorithm\n4. Alice allows the algorithm for C2D for that data asset\n5. Bob acquires datatokens for data and algorithm\n6. Bob starts a compute job using a free C2D environment (no provider fees)\n7. Bob monitors logs / algorithm output\n\nLet's go through each step.\n\n## 1. Setup\n\n### 1.1 Install Ocean\n\nFirst, ensure that you've [installed Ocean](install.md)\n\n### 1.2 Install matplotlib\n\nThis example uses C2D to create a regression model. We'll use the library `matplotlib` to visualize it.\n\nIn the same console:\n```console\n#ensure you're in the Python virtualenv\nsource venv/bin/activate\n\n#install matplotlib\npip install matplotlib\n```\n\n### 1.3 Setup remotely\n\nFollow [setup-remote.md](setup-remote.md).\n\n## 2. Alice publishes dataset\n\nIn the same python console:\n\n```python\n\n# Publish data NFT, datatoken, and asset for dataset based on url\n\n# ocean.py offers multiple file object types. A simple url file is enough for here\nfrom ocean_lib.structures.file_objects import UrlFile\nDATA_url_file = UrlFile(\n    url=\"https://raw.githubusercontent.com/oceanprotocol/c2d-examples/main/branin_and_gpr/branin.arff\"\n)\n\nname = \"Branin dataset\"\n(DATA_data_nft, DATA_datatoken, DATA_ddo) = ocean.assets.create_url_asset(name, DATA_url_file.url, {\"from\": alice}, with_compute=True, wait_for_aqua=True)\nprint(f\"DATA_data_nft address = '{DATA_data_nft.address}'\")\nprint(f\"DATA_datatoken address = '{DATA_datatoken.address}'\")\n\nprint(f\"DATA_ddo did = '{DATA_ddo.did}'\")\n```\n\nTo customise the privacy and accessibility of your compute service, add the `compute_values` argument to\n`create_url_asset` to set values according to the [DDO specs](https://docs.oceanprotocol.com/core-concepts/did-ddo).\nThe function assumes the documented defaults.\n\n## 3. Alice publishes an algorithm\n\nIn the same Python console:\n\n```python\n# Publish data NFT & datatoken for algorithm\nALGO_url = \"https://raw.githubusercontent.com/oceanprotocol/c2d-examples/main/branin_and_gpr/gpr.py\"\n\nname = \"grp\"\n(ALGO_data_nft, ALGO_datatoken, ALGO_ddo) = ocean.assets.create_algo_asset(name, ALGO_url, {\"from\": alice}, wait_for_aqua=True)\n\nprint(f\"ALGO_data_nft address = '{ALGO_data_nft.address}'\")\nprint(f\"ALGO_datatoken address = '{ALGO_datatoken.address}'\")\nprint(f\"ALGO_ddo did = '{ALGO_ddo.did}'\")\n```\n\n## 4. Alice allows the algorithm for C2D for that data asset\n\nIn the same Python console:\n```python\ncompute_service = DATA_ddo.services[1]\ncompute_service.add_publisher_trusted_algorithm(ALGO_ddo)\nDATA_ddo = ocean.assets.update(DATA_ddo, {\"from\": alice})\n\n```\n\n## 5. Bob acquires datatokens for data and algorithm\n\nIn the same Python console:\n```python\n# Alice mints DATA datatokens and ALGO datatokens to Bob.\n# Alternatively, Bob might have bought these in a market.\nfrom ocean_lib.ocean.util import to_wei\nDATA_datatoken.mint(bob, to_wei(5), {\"from\": alice})\nALGO_datatoken.mint(bob, to_wei(5), {\"from\": alice})\n```\n\n## 6. Bob starts a compute job using a free C2D environment\n\nOnly inputs needed: DATA_did, ALGO_did. Everything else can get computed as needed.\nFor demo purposes, we will use the free C2D environment, which requires no provider fees.\n\nIn the same Python console:\n```python\n# Convenience variables\nDATA_did = DATA_ddo.did\nALGO_did = ALGO_ddo.did\n\n# Operate on updated and indexed assets\nDATA_ddo = ocean.assets.resolve(DATA_did)\nALGO_ddo = ocean.assets.resolve(ALGO_did)\n\ncompute_service = DATA_ddo.services[1]\nalgo_service = ALGO_ddo.services[0]\nfree_c2d_env = ocean.compute.get_free_c2d_environment(compute_service.service_endpoint, DATA_ddo.chain_id)\n\nfrom datetime import datetime, timedelta, timezone\nfrom ocean_lib.models.compute_input import ComputeInput\n\nDATA_compute_input = ComputeInput(DATA_ddo, compute_service)\nALGO_compute_input = ComputeInput(ALGO_ddo, algo_service)\n\n# Pay for dataset and algo for 1 day\ndatasets, algorithm = ocean.assets.pay_for_compute_service(\n    datasets=[DATA_compute_input],\n    algorithm_data=ALGO_compute_input,\n    consume_market_order_fee_address=bob.address,\n    tx_dict={\"from\": bob},\n    compute_environment=free_c2d_env[\"id\"],\n    valid_until=int((datetime.now(timezone.utc) + timedelta(days=1)).timestamp()),\n    consumer_address=free_c2d_env[\"consumerAddress\"],\n)\nassert datasets, \"pay for dataset unsuccessful\"\nassert algorithm, \"pay for algorithm unsuccessful\"\n\n# Start compute job\njob_id = ocean.compute.start(\n    consumer_wallet=bob,\n    dataset=datasets[0],\n    compute_environment=free_c2d_env[\"id\"],\n    algorithm=algorithm,\n)\nprint(f\"Started compute job with id: {job_id}\")\n```\n\n## 7. Bob monitors logs / algorithm output\n\nIn the same Python console, you can check the job status as many times as needed:\n\n```python\n# Wait until job is done\nimport time\nfrom decimal import Decimal\nsucceeded = False\nfor _ in range(0, 200):\n    status = ocean.compute.status(DATA_ddo, compute_service, job_id, bob)\n    if status.get(\"dateFinished\") and Decimal(status[\"dateFinished\"]) > 0:\n        succeeded = True\n        break\n    time.sleep(5)\n```\n\nThis will output the status of the current job.\nHere is a list of possible results: [Operator Service Status description](https://github.com/oceanprotocol/operator-service/blob/main/API.md#status-description).\n\nOnce the returned status dictionary contains the `dateFinished` key, Bob can retrieve the job results using ocean.compute.result or, more specifically, just the output if the job was successful.\nFor the purpose of this tutorial, let's choose the second option.\n\n```python\n# Retrieve algorithm output and log files\noutput = ocean.compute.compute_job_result_logs(\n    DATA_ddo, compute_service, job_id, bob\n)[0]\n\nimport pickle\nmodel = pickle.loads(output)  # the gaussian model result\nassert len(model) > 0, \"unpickle result unsuccessful\"\n```\n\nYou can use the result however you like. For the purpose of this example, let's plot it.\n\n```python\nimport numpy\nfrom matplotlib import pyplot\n\nX0_vec = numpy.linspace(-5., 10., 15)\nX1_vec = numpy.linspace(0., 15., 15)\nX0, X1 = numpy.meshgrid(X0_vec, X1_vec)\nb, c, t = 0.12918450914398066, 1.5915494309189535, 0.039788735772973836\nu = X1 - b * X0 ** 2 + c * X0 - 6\nr = 10. * (1. - t) * numpy.cos(X0) + 10\nZ = u ** 2 + r\n\nfig, ax = pyplot.subplots(subplot_kw={\"projection\": \"3d\"})\nax.scatter(X0, X1, model, c=\"r\", label=\"model\")\npyplot.title(\"Data + model\")\npyplot.show()  # or pyplot.savefig(\"test.png\") to save the plot as a .png file instead\n```\n\nYou should see something like this:\n\n![test](https://user-images.githubusercontent.com/4101015/134895548-82e8ede8-d0db-433a-b37e-694de390bca3.png)\n\n## Appendix. Tips & tricks\n\nThis README has a simple ML algorithm. However, Ocean C2D is not limited to usage in ML. The file [c2d-flow-more-examples.md](https://github.com/oceanprotocol/ocean.py/blob/v4main/READMEs/c2d-flow-more-examples.md) has examples from vision and other fields.\n\nIn the \"publish algorithm\" step, to replace the sample algorithm with another one:\n\n- Use one of the standard [Ocean algo_dockers images](https://github.com/oceanprotocol/algo_dockers) or publish a custom docker image.\n- Use the image name and tag in the `container` part of the algorithm metadata.\n- The image must have basic support for installing dependencies. E.g. \"pip\" for the case of Python. You can use other languages, of course.\n- More info: https://docs.oceanprotocol.com/tutorials/compute-to-data-algorithms/)\n\nThe function to `pay_for_compute_service` automates order starting, order reusing and performs all the necessary Provider and on-chain requests.\nIt modifies the contents of the given ComputeInput as follows:\n\n- If the dataset/algorithm contains a `transfer_tx_id` property, it will try to reuse that previous transfer id. If provider fees have expired but the order is still valid, then the order is reused on-chain.\n- If the dataset/algorithm does not contain a `transfer_tx_id` or the order has expired (based on the Provider's response), then one new order will be created.\n\nThis means you can reuse the same ComputeInput and you don't need to regenerate it everytime it is sent to `pay_for_compute_service`. This step makes sure you are not paying unnecessary or duplicated fees.\n\nIf you wish to upgrade the compute resources, you can use any (paid) C2D environment.\nInspect the results of `ocean.ocean_compute.get_c2d_environments(service.service_endpoint, DATA_ddo.chain_id)` and `ocean.retrieve_provider_fees_for_compute(datasets, algorithm_data, consumer_address, compute_environment, duration)` for a preview of what you will pay.\nDon't forget to handle any minting, allowance or approvals on the desired token to ensure transactions pass.\n"
  },
  {
    "path": "READMEs/custody-light-flow.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Custody-light flow\n\nIt allows orgs to buy & consume data without having custody of assets.\n\nWe assume you've already (a) [installed Ocean](install.md), and (b) done [local setup](setup-local.md) or [remote setup](setup-remote.md). This flow works for either one, without any changes between them (!)\n\nThis flow is split in two sections:\n- free steps: publish free asset with a Datatoken 2 (enterprise template), then consume;\n- priced steps: publish a priced asset attached to a Datatoken 2 (enterprise template), then buy / consume.\n\nLet's go!\n\n\n## Free steps\n\nSteps in this flow:\n\n1. Alice publishes a free asset\n2. Bob dispenses funds from the asset's pricing schema\n3. Bob consumes the asset\n\n### 1. Alice publishes a free asset\n\nIn the same Python console:\n```python\n#data info\nname = \"Branin dataset\"\nurl = \"https://raw.githubusercontent.com/trentmc/branin/main/branin.arff\"\n\n#create data asset\nfrom ocean_lib.models.dispenser import DispenserArguments\nfrom ocean_lib.ocean.util import to_wei\n\n(data_nft, datatoken, ddo) = ocean.assets.create_url_asset(name, url, {\"from\": alice}, dt_template_index=2, pricing_schema_args=DispenserArguments(to_wei(1), to_wei(1)))\n\n#print\nprint(\"Just published a free asset:\")\nprint(f\"  data_nft: symbol={data_nft.symbol}, address={data_nft.address}\")\nprint(f\"  datatoken: symbol={datatoken.symbol}, address={datatoken.address}\")\nprint(f\"  did={ddo.did}\")\n```\n\n### 2. Bob dispenses funds from the asset's pricing schema\n\nBob wants to consume Alice's asset. He can dispense 1.0 datatokens to complete his job.\nBelow, we show the possible approach:\n\n```python\n\nprovider_fees = ocean.retrieve_provider_fees(\n    ddo, ddo.services[0], publisher_wallet=bob\n)\n\ntx = datatoken.dispense_and_order(provider_fees, {\"from\": bob}, consumer=bob.address, service_index=0)\n\n```\n\n### 3. Bob consumes the asset\n\nBob now has the transaction receipt to prove that he dispensed funds! Time to download the dataset and use it.\n\n\nIn the same Python console:\n```python\n# Bob downloads the file. If the connection breaks, Bob can try again\nasset_dir = ocean.assets.download_asset(ddo, bob, './', tx.transactionHash.hex())\n\nimport os\nfile_name = os.path.join(asset_dir, \"file0\")\n```\n\nLet's check that the file is downloaded. In a new console:\n\n```console\ncd my_project/datafile.did:op:0xAf07...\nls branin.arff\n```\n\n\n## Priced steps\n\nSteps in this flow:\n\n1. Alice publishes a priced asset\n2. Bob buys funds from the asset's pricing schema\n3. Bob consumes the asset\n\n### 1. Alice publishes a free asset\n\nIn the same Python console:\n```python\n# data info\nname = \"Branin dataset\"\nurl = \"https://raw.githubusercontent.com/trentmc/branin/main/branin.arff\"\n\n# create data asset\nfrom ocean_lib.models.fixed_rate_exchange import ExchangeArguments\nfrom ocean_lib.ocean.util import to_wei\n\n(data_nft, datatoken, ddo) = ocean.assets.create_url_asset(name, url, {\"from\": alice}, dt_template_index=2, pricing_schema_args=ExchangeArguments(\n            rate=to_wei(3), base_token_addr=ocean.OCEAN_address, dt_decimals=18\n        ),)\n\n#print\nprint(\"Just published a priced asset:\")\nprint(f\"  data_nft: symbol={data_nft.symbol}, address={data_nft.address}\")\nprint(f\"  datatoken: symbol={datatoken.symbol}, address={datatoken.address}\")\nprint(f\"  did={ddo.did}\")\n```\n\n### 2. Bob buys funds from the asset's pricing schema\n\nBob wants to consume Alice's asset. He can buy 1.0 datatokens to complete his job.\nBelow, we show the possible approach:\n\n```python\n\nprovider_fees = ocean.retrieve_provider_fees(\n    ddo, ddo.services[0], publisher_wallet=bob\n)\nexchange = datatoken.get_exchanges()[0]\nOCEAN = ocean.OCEAN_token\nOCEAN.approve(\n        datatoken.address,\n        to_wei(10),\n        {\"from\": bob},\n)\nOCEAN.approve(\n    exchange.address,\n    to_wei(10),\n    {\"from\": bob},\n)\ntx = datatoken.buy_DT_and_order(provider_fees, exchange, {\"from\": bob}, consumer=bob.address, service_index=0)\n\n```\n\n### 3. Bob consumes the asset\n\nBob now has the transaction receipt to prove that he bought funds from the exchange! Time to download the dataset and use it.\n\n\nIn the same Python console:\n```python\n# Bob downloads the file. If the connection breaks, Bob can try again\nasset_dir = ocean.assets.download_asset(ddo, bob, './', tx.transactionHash.hex())\n\nimport os\nfile_name = os.path.join(asset_dir, \"file0\")\n```\n\nLet's check that the file is downloaded. In a new console:\n\n```console\ncd my_project/datafile.did:op:0xAf07...\nls branin.arff\n```\n"
  },
  {
    "path": "READMEs/developers.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Developing ocean.py\n\nThis README is how to further _develop_ ocean.py. (Compare to the quickstarts which show how to _use_ it.)\nSteps:\n\n1.  **Install dependencies**\n2.  **Run barge services**\n3.  **Set up contracts**\n4.  **Test**\n5.  **Merge** the changes via a PR\n6.  **Release**\n\n## 1. Install dependencies\n\n### Prerequisites\n\n-   Linux/MacOS\n-   Docker, [allowing non-root users](https://www.thegeekdiary.com/run-docker-as-a-non-root-user/)\n-   Python 3.8.5+\n\n### Do Install\n\nIn a new console that we'll call the _work_ console (as we'll use it later):\n\n```console\n# Clone the repo and enter into it\ngit clone https://github.com/oceanprotocol/ocean.py\ncd ocean.py\n\n# Install OS dependencies\nsudo apt-get install -y python3-dev gcc\n\n# Initialize virtual environment and activate it.\n# Make sure your Python version inside the venv is >=3.8.\npython3 -m venv venv\nsource venv/bin/activate\n\n# Install modules in the environment.\npip install -r requirements_dev.txt\n```\n\n## 2. Run barge services\n\nIn a new console:\n\n```console\n# grab repo\ngit clone https://github.com/oceanprotocol/barge\ncd barge\n\n# clean up old containers (to be sure)\ndocker system prune -a --volumes\n\n# for support of type 2 transactions\nexport GANACHE_FORK=london\n\n# Run barge: start Ganache, Provider, Aquarius; deploy contracts; update ~/.ocean\n# The `--with-c2d` option tells barge to include the Compute-to-Data backend\n./start_ocean.sh --with-c2d\n```\n\n(Or, [run services separately](services.md).)\n\n## 3. Set up contracts\n\nIn work console:\n\n```console\n# set private keys of two local (ganache) accounts\nexport TEST_PRIVATE_KEY1=0x8467415bb2ba7c91084d932276214b11a3dd9bdb2930fefa194b666dd8020b99\nexport TEST_PRIVATE_KEY2=0x1d751ded5a32226054cd2e71261039b65afb9ee1c746d055dd699b1150a5befc\n\n# needed to mint fake OCEAN for testing with ganache\nexport FACTORY_DEPLOYER_PRIVATE_KEY=0xc594c6e5def4bab63ac29eed19a134c130388f74f019bc74b8f4389df2837a58\n```\n\nSome tests run on Mumbai (e.g. test_mumbai.py), which need fake MATIC. So you also need:\n```console\n# set private keys of two remote accounts\nexport REMOTE_TEST_PRIVATE_KEY1=<your remote private key 1>\nexport REMOTE_TEST_PRIVATE_KEY2=<your remote private key 2>\n```\n\nThese keys aren't public because bots could eat the fake MATIC. You need to generate your own, and fill them with a faucet; see instructions in remote setup README. Or, [access-protected OPF keys](https://github.com/oceanprotocol/private-keys/blob/main/README.md)).\n\n## 4. Test\n\nIn work console:\n```console\n# run a single test\npytest ocean_lib/models/test/test_data_nft_factory.py::test_start_multiple_order\n\n# run all tests in a file\npytest ocean_lib/models/test/test_data_nft_factory.py\n\n# run all regular tests; see details on pytest markers to select specific suites\npytest\n```\n\nThe README tests are special. Here's how to run them:\n```console\n# need to auto-generate READMEs first\nmkcodes --github --output tests/generated-readmes/test_{name}.{ext} READMEs\n\n# then run the tests\npytest tests/readmes/test_readmes.py\npytest /tests/integration/remote/test_mumbai_readme.py\n```\n\nFor envvars that aren't set, `pytest` uses values in `pytest.ini`.\n\n\n## 5. Merge\n\nMerge the changes via a pull request (PR) etc.\n\nSpecifically, [follow this workflow](https://docs.oceanprotocol.com/concepts/contributing/#fix-or-improve-core-software).\n\n## 6. Release\n\nRelease for pip etc.\n\nSpecifically, [follow the Release Process instructions](./release-process.md).\n\n## 7. Appendix: More tests\n\n### 7.1 Pre-commit hooks\n\nIn main console (with venv on):\n\n```console\npre-commit install\n```\n\nNow, this will auto-apply isort (import sorting), flake8 (linting) and black (automatic code formatting) to commits. Black formatting is the standard and is checked as part of pull requests.\n\n## 8. Appendix: Contributing to docs\n\nYou are welcome to contribute to ocean.py docs and READMEs. For clean markdowns in the READMEs folder, we use the `remark` tool for automatic markdown formatting.\nOCEAN has an official repository containing remark settings, so please follow the instructions [here](https://github.com/oceanprotocol/ocean-remark).\n"
  },
  {
    "path": "READMEs/df.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n# Quickstart: Data Farming Flow\n\nThis README shows how to do steps in Ocean Data Farming (DF), where you curate data assets to earn rewards. It also helps to democratize \"wash consume\" until it becomes unprofitable.\n\nHere are the steps:\n\n1. Setup, in Ganache\n2. Lock OCEAN for veOCEAN\n3. Publish dataset & exchange\n4. Allocate veOCEAN to dataset\n5. Fake-consume data\n6. Collect OCEAN rewards\n7. Repeat steps 1-6, for Eth mainnet\n\nLet's go through each step.\n\n## 1. Setup\n\nEnsure that you've already (a) [installed Ocean](install.md), and (b) [set up locally](setup-local.md).\n\n\n## 2. Lock OCEAN for veOCEAN\n\nFirst, let's set some key parameters for veOCEAN and DF. On Ganache, you can use these values as-is. But on Eth mainnet, you must choose your own. In the same Python console:\n```python\n# On your asset, your DCV = DT_price * num_consumes\n# Your asset gets rewards pro-rata for its DCV compared to other assets' DCVs.\nDT_price = 100.0 # number of OCEAN needed to buy one datatoken\nnum_consumes = 3\n\n# This is how much OCEAN to lock into veOCEAN. It can be small if you're\n# the only staker on your asset. If others stake on your asset, your\n# rewards are pro-rate compared to others' stake in your asset.\namt_OCEAN_lock = 10.0\n```\n\nNow, let's lock OCEAN for veOCEAN. In the same Python console:\n```python\n# simulate passage of time, until next Thursday, the start of DF(X)\nweb3 = ocean.config_dict[\"web3_instance\"]\nprovider = web3.provider\nlatest_block = web3.eth.get_block(\"latest\")\n\nWEEK = 7 * 86400 # seconds in a week\nt0 = latest_block.timestamp\nt1 = t0 // WEEK * WEEK + WEEK # this is a Thursday, because Jan 1 1970 was\nt2 = t1 + WEEK\nprovider.make_request(\"evm_increaseTime\", [(t1 - t0)])\n\n#we're now at the beginning of the week. So, lock\nveOCEAN = ocean.veOCEAN\nOCEAN.approve(veOCEAN.address, to_wei(amt_OCEAN_lock), {\"from\" : alice})\n\nimport math\nweb3 = ocean.config_dict[\"web3_instance\"]\nlatest_block = web3.eth.get_block(\"latest\")\nveOCEAN.withdraw({\n    \"from\": alice,\n    \"gas\": latest_block.gasLimit,\n    \"gasPrice\": math.ceil(latest_block[\"baseFeePerGas\"] * 1.2),\n}) # withdraw old tokens first\n\nlatest_block = web3.eth.get_block(\"latest\")\nveOCEAN.create_lock(\n    to_wei(amt_OCEAN_lock),\n    t2,\n    {\n        \"from\": alice,\n        \"gas\": latest_block.gasLimit,\n        \"gasPrice\": math.ceil(latest_block[\"baseFeePerGas\"] * 1.2),\n    })\n```\n\n\n## 3. Publish Dataset & Exchange\n\nIn the same Python console:\n```python\n#data info\nname = \"Branin dataset\"\nurl = \"https://raw.githubusercontent.com/trentmc/branin/main/branin.arff\"\n\n#create data asset\n(data_NFT, DT, ddo) = ocean.assets.create_url_asset(name, url, {\"from\": alice}, wait_for_aqua=False)\nprint(f\"Just published asset, with data_NFT.address={data_NFT.address}\")\n\n#create exchange\nexchange = DT.create_exchange({\"from\": alice}, to_wei(DT_price), OCEAN.address)\n\n#make datatokens available on the exchange\nDT.mint(alice, to_wei(num_consumes), {\"from\": alice})\nDT.approve(exchange.address, to_wei(num_consumes), {\"from\": alice})\n```\n\n\n## 4. Stake on dataset\n\nTo stake, you allocate veOCEAN to dataset. In the same Python console:\n```python\namt_allocate = 100 #total allocation must be <= 10000 (wei)\nocean.ve_allocate.setAllocation(amt_allocate, data_NFT.address, web3.eth.chain_id, {\"from\": alice})\n```\n\n## 5. Fake-consume data\n\n\"Wash consuming\" is when the publisher fake-consumes data to drive data consume volume (DCV) to get more rewards. Not healthy for the ecosystem long-term. Good news: if consume fee > weekly rewards, then wash consume becomes unprofitable. DF is set up to make this happen by DF29 (if not sooner). [Details](https://twitter.com/trentmc0/status/1587527525529358336).\n\nIn the meantime, this README helps level the playing field around wash consume. This step shows how to do fake-consume.\n\n```python\n# Alice buys datatokens from herself\nOCEAN_pay = DT_price * num_consumes\nOCEAN_alice = from_wei(OCEAN.balanceOf(alice))\nassert OCEAN_alice >= OCEAN_pay, f\"Have just {OCEAN_alice} OCEAN\"\n\nOCEAN.approve(exchange.address, to_wei(OCEAN_alice), {\"from\": alice})\nexchange.buy_DT(to_wei(num_consumes), {\"from\": alice})\n\nDT_bal = from_wei(DT.balanceOf(alice))\nassert DT_bal >= num_consumes, \\\n    f\"Have {DT_bal} datatokens, too few for {num_consumes} consumes\"\n\n# Alice sends datatokens to the service, to get access. This is the \"consume\".\nfor i in range(num_consumes):\n    print(f\"Consume #{i+1}/{num_consumes}...\")\n    ocean.assets.pay_for_access_service(ddo, {\"from\": alice})\n    #don't need to call e.g. ocean.assets.download_asset() since wash-consuming\n```\n\n## 6. Collect OCEAN rewards\n\nIn the same Python console:\n\n```python\n#simulate passage of time, until next Thursday, which is the start of DF(X+1)\nWEEK = 7 * 86400 # seconds in a week\n\nlatest_block = web3.eth.get_block(\"latest\")\nt0 = latest_block.timestamp\nt1 = t0 // WEEK * WEEK + WEEK\nt2 = t1 + WEEK\nprovider.make_request(\"evm_increaseTime\", [(t1 - t0)])\n\n#Rewards can be claimed via code or webapp, at your leisure. Let's do it now.\nOCEAN_before = from_wei(OCEAN.balanceOf(alice))\nocean.ve_fee_distributor.claim({\n    \"from\": alice,\n    \"gas\": latest_block.gasLimit,\n    \"gasPrice\": math.ceil(latest_block[\"baseFeePerGas\"] * 1.2),\n})\nOCEAN_after = from_wei(OCEAN.balanceOf(alice))\nprint(f\"Just claimed {OCEAN_after - OCEAN_before} OCEAN rewards\")\n```\n\n## 7. Repeat steps 1-6, for Eth mainnet\n\nFirst, you'll need to set up remotely. This will be like [setup-remote.md](setup-remote.md), but for Eth mainnet.\n\nWe leave this as an exercise to the reader:)\n\nHappy Data Farming!\n\n\n## Appendix.\n\nAt the beginning of this flows, we created an `ocean` object, which is an instance of class [`Ocean`](https://github.com/oceanprotocol/ocean.py/blob/main/ocean_lib/ocean/ocean.py).\n\nIt provides convenient access to [DF](https://github.com/oceanprotocol/ocean.py/tree/main/ocean_lib/models/df) & [VE](https://github.com/oceanprotocol/ocean.py/tree/main/ocean_lib/models/ve) Python objects that which wrap [DF](https://github.com/oceanprotocol/contracts/tree/main/contracts/df) & [VE](https://github.com/oceanprotocol/contracts/tree/main/contracts/ve) Solidity contracts:\n- `ocean.ve_ocean` or `ocean.veOCEAN -> VeOcean`\n- `ocean.df_rewards -> DFRewards`\n- `ocean.df_strategy_v1 -> DFStrategyV1`\n- `ocean.smart_wallet_checker -> SmartWalletChecker`\n- `ocean.ve_allocate -> VeAllocate`\n- `ocean.ve_delegation -> VeDelegation`\n- `ocean.ve_fee_distributor -> VeFeeDistributor`\n- `ocean.ve_fee_estimate(self) -> VeFeeEstimate`\n\n\n"
  },
  {
    "path": "READMEs/gas-strategy-remote.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Quickstart: Use Specific Gas Strategy for Remote Networks\n\nThis quickstart illustrates the definition of gas strategy in ocean-lib stack in order to\nconfirm the transactions on blockchain as soon as possible in case the network is congested.\n\nHere are the steps:\n\n1.  Setup\n2.  Define gas strategy\n3.  Alice publishes the asset using gas strategy\n\nLet's go through each step.\n\n## 1. Setup\n\nEnsure that you've already (a) [installed Ocean](install.md), and (b) [set up remotely](setup-remote.md).\n\n## 2. Define gas strategy\n\nFees are defined for `polygon` & `mumbai` networks.\n\n```python\nfrom ocean_lib.web3_internal.utils import get_gas_fees\n\npriority_fee, max_fee = get_gas_fees()\n```\n\n## 3. Alice publishes the asset using gas strategy\n\nThe gas strategy can be added to any `tx_dict`, and this is just an example of usage.\n```python\n#data info\nname = \"Branin dataset\"\nurl = \"https://raw.githubusercontent.com/trentmc/branin/main/branin.arff\"\ntx_dict = {\n        \"from\": alice,\n        \"maxPriorityFeePerGas\": priority_fee,\n        \"maxFeePerGas\": max_fee,\n}\n\n#create data asset\n(data_nft, datatoken, ddo) = ocean.assets.create_url_asset(\n    name,\n    url,\n    tx_dict=tx_dict\n)\n\n#print\nprint(\"Just published a data asset:\")\nprint(f\"  data_nft: symbol={data_nft.symbol}, address={data_nft.address}\")\nprint(f\"  datatoken: symbol={datatoken.symbol}, address={datatoken.address}\")\nprint(f\"  did={ddo.did}\")\n```\n\n"
  },
  {
    "path": "READMEs/install.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n# Install Ocean\n\n## Prerequisites\n\n-   Linux, MacOS, or Windows\n-   [Docker](https://docs.docker.com/engine/install/), [Docker Compose](https://docs.docker.com/compose/install/), [allowing non-root users](https://www.thegeekdiary.com/run-docker-as-a-non-root-user/)\n-   Python 3.8.5 - Python 3.10.4, Python 3.11 with some manual alterations\n\n## Install ocean.py library\n\n(If you have issues, see \"Potential issues & workarounds\" section below.)\n\nIn a new console:\n\n```console\n# Create your working directory\nmkdir my_project\ncd my_project\n\n# Initialize virtual environment and activate it. Install artifacts.\n# Make sure your Python version inside the venv is >=3.8.\n# Anaconda is not fully supported for now, please use venv\npython3 -m venv venv\nsource venv/bin/activate\n\n# Avoid errors for the step that follows\npip install wheel\n\n# Install Ocean library.\npip install ocean-lib\n```\n\n## Potential issues & workarounds\n\nIssue: M1 * `coincurve` or `cryptography`\n- If you have an Apple M1 processor, `coincurve` and `cryptography` installation may fail due missing packages, which come pre-packaged in other operating systems.\n- Workaround: ensure you have `autoconf`, `automake`, `libtool` and `pkg-config` installed, e.g. using Homebrew or MacPorts.\n\nIssue: Could not build wheels for coincurve\n- Reasons for this happening are usually missing dependencies.\n- Workaround:\n  - make sure you have the OS-level development libraries for building Python packages: `python3-dev` and `build-essential` (install e.g. using apt-get)\n  - install the OS-level `libsecp256k1-dev` library (e.g. using apt-get)\n  - install pyproject.toml separately, e.g. `pip install pyproject-toml`\n  - if ocean-lib installation still fails, install coincurve separately e.g. `pip install coincurve`, then retry\n\nIssue: MacOS \"Unsupported Architecture\"\n- If you run MacOS, you may encounter an \"Unsupported Architecture\" issue.\n- Workaround: install including ARCHFLAGS: `ARCHFLAGS=\"-arch x86_64\" pip install ocean-lib`. [Details](https://github.com/oceanprotocol/ocean.py/issues/486).\n\nIssue: Dependencies and Python 3.11\n\n- ocean.py depends on the `parsimonious` package. In turn, `parsimonious` depends on `getargsspec`, which doesn't support Python 3.11. The workaround: open the package's expressions.py file (e.g. in ./venv/lib/python3.11/site-packages/parsimonious/expressions.py), and change the line `import getfullargspec as getargsspec` instead of the regular import.\n\n## Next step\n\nYou've now installed Ocean, great!\n\nNext step is setup:\n- [Remote](setup-remote.md) (Win, MacOS, Linux)\n- *or* [Local](setup-local.md) (Linux only)\n\n"
  },
  {
    "path": "READMEs/key-value-private.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Quickstart: Private Sharing of On-Chain Data\n\n## Introduction\nThis quickstart describes how to use Ocean data NFTs to publish data on-chain, then privately share it to multiple parties.\n\nIt can be used for:\n1. **Sharing AI models** of small to medium size, to specified parties.\n2. **Sharing AI model predictions** to specified parties.\n3. **[\"Soulbound Tokens\"](https://papers.ssrn.com/sol3/Delivery.cfm/SSRN_ID4105763_code1186331.pdf?abstractid=4105763&mirid=1)** approach to Web3 identity, where an individual's attributes are fields in one (or more) data NFTs.\n4. **Profile NFTs / \"Login with Web3\"** where a Dapp accesses userdata. In this case, the code would be running in the browser via [pyscript](https://www.pyscript.org); or it would be an equivalent flow using JS not Python. This can be viewed as a special case of (2).\n\nTo generalize, this flow is appropriate for:\n- **Small to medium-sized datasets.** For larger datasets, store the data off-chain and share via Ocean datatokens.\n- **When the data sharer knows (or can compute) the recipient's public key.** When this isn't known - such as for faucets to serve free data to anyone, or for selling priced data to anyone, then use Ocean datatokens.\n\n## Steps\n\nThe quickstart follows these steps, for an example of privately sharing an AI model.\n\nSteps by AI modeler (Alice):\n1. Setup\n2. Publish data NFT\n3. Encrypt & store on-chain AI model\n4. Share encryption key via chain\n\nSteps by AI model retriever (Bob):\n5. Get encryption key via chain\n6. Retrieve from chain & decrypt AI model\n\n## 1. Setup\n\nEnsure that you've already (a) [installed Ocean](install.md), and (b) [set up locally](setup-local.md) or [remotely](setup-remote.md).\n\n## 2. Publish data NFT\n\nHere, we publish a data NFT like elsewhere. To make it a soulbound token (SBT). we set `transferable=False`.\n\nIn the Python console:\n```python\n# Publish an NFT token. Note \"transferable=False\"\ndata_nft = ocean.data_nft_factory.create({\"from\": alice}, 'NFT1', 'NFT1', transferable=False)\n```\n\n## 3. Encrypt & store on-chain AI model\n\nHere, we'll symmetrically encrypt an AI model, then store it as a key-value pair in a data NFT on-chain.\n\nIn the Python console:\n```python\n# Key-value pair\nmodel_label = \"my_MLP\"\nmodel_value = \"<insert MLP weights here>\"\n\n# Compute a symmetric key: unique to this (nft, nft field) and (your priv key)\n# Therefore you can calculate it anytime\nfrom ocean_lib.ocean import crypto\nsymkey = crypto.calc_symkey(data_nft.address + model_label + alice._private_key.hex())\n\n# Symmetrically encrypt AI model\nmodel_value_symenc = crypto.sym_encrypt(model_value, symkey)\n\n# Save model to chain\ndata_nft.set_data(model_label, model_value_symenc, {\"from\": alice})\n```\n\n## 4. Share encryption key via chain\n\nThere are many possible ways for Alice to share the symkey to Bob. Here, Alice shares it securely on a public channel by encrypting the symkey in a way that only Bob can decrypt:\n- The public channel is on the same data NFT, on-chain\n- So that only Bob can decrypt: Alice asymetricallys encrypt the symkey with Bob's public key, for Bob to decrypt with his private key.\n\nIn the Python console:\n```python\n# Get Bob's public key. There are various ways; see appendix.\npubkey = crypto.calc_pubkey(bob._private_key.hex())\n\n# Asymmetrically encrypt symkey, using Bob's public key\nsymkey_asymenc = crypto.asym_encrypt(symkey, pubkey)\n\n# Save asymetrically-encrypted symkey to chain\ndata_nft.set_data(\"symkey\", symkey_asymenc, {\"from\": alice})\n```\n\n\n## 5. Get encryption key via chain\n\nWhereas the first four steps were done by the AI model sharer (Alice), the remaining steps are done by the AI model receiver (Bob). You're now Bob.\n\nIn the Python console:\n```python\n# Retrieve the asymetrically-encrypted symkey from chain\nsymkey_asymenc2 = data_nft.get_data(\"symkey\")\n\n# Asymetrically decrypt symkey, with Bob's private key\nsymkey2 = crypto.asym_decrypt(symkey_asymenc2, bob._private_key.hex())\n```\n\n## 6. Retrieve from chain & decrypt AI model\n\nIn the Python console:\n```python\n# Retrieve the symetrically-encrypted model from chain\nmodel_value_symenc2 = data_nft.get_data(model_label)\n\n# Symetrically-decrypt the model, with the symkey retrieved in step 5\nmodel_value2 = crypto.sym_decrypt(model_value_symenc2, symkey2)\n\nprint(f\"Loaded model {model_label} = {model_value2}\")\n```\n\n\n## Appendix\n\nStep 4 gave one way for Alice to get the Dapp's public key; step 5 gave one way for the Dapp to get the encrypted symkey. Here are more options.\n\nOn computing public keys:\n- If you have the private_key, you can compute the public_key (used above)\n- Hardware wallets don't expose private_keys. And, while they do expose a _root_ public_key, you shouldn't publicly share those because it lets anyone see all your wallets\n- However, you _can_ compute anyone's public_key from any tx. This is a general solution. Conveniently, Etherscan shows it too.\n\nPossible ways for Alice to get Dapp's public key:\n- Alice auto-computes from any of Dapp's previous txs.\n- Alice retrieves it from a public-ish registry or api, e.g. etherscan\n- Dapp computes it from private_key or from past tx, then shares.\n\nPossible ways for Alice to share an encrypted symkey, or for Dapp to share public_key:\n- Directly client-side\n  - Client-side: in a browser with Metamask - [example by FELToken](https://betterprogramming.pub/exchanging-encrypted-data-on-blockchain-using-metamask-a2e65a9a896c). This is a good choice because it does no on-chain txs.\n  - Client-side: in a script. Like done in step 4 above for public key\n- Over a public channel:\n  - Public channel: write a new key-value pair on the same data NFT. Like done in step 5 above for encrypted symkey. This is a good choice because the Dapp can access the info in future sessions without extra work.\n  - Public channel: a new data NFT for each message\n  - Public channel: traditional: http, email, or any messaging medium\n\n\n"
  },
  {
    "path": "READMEs/key-value-public.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Quickstart: On-Chain Key-Value Store for Public Sharing\n\nData NFTs can store arbitrary key-value pairs, to be an on-chain key-value store.\n\nThey can be used for:\n1. **Publicly sharing AI models** of small to medium size\n2. **Publicly sharing AI model predictions**\n3. **Comments & ratings in Dapps**\n4. **Digital Attestations**, e.g. for verifiable credentials\n\nThis flow is appropriate for:\n- **Public data**. The next README will explore for private sharing.\n- **Small to medium-sized datasets.** For larger datasets, store the data off-chain and share via Ocean datatokens.\n\nHere are the steps:\n\n1. Setup\n2. Publish data NFT\n3. Add key-value pair to data NFT\n4. Retrieve value from data NFT\n\n## 1. Setup\n\nEnsure that you've already (a) [installed Ocean](install.md), and (b) [set up locally](setup-local.md) or [remotely](setup-remote.md).\n\n## 2. Publish data NFT\n\nIn Python console:\n```python\ndata_nft = ocean.data_nft_factory.create({\"from\": alice}, 'NFT1', 'NFT1')\n```\n\n## 3. Add key-value pair to data NFT\n\n```python\n# Key-value pair\nmodel_key = \"my_MLP\"\nmodel_value = \"<insert MLP weights here>\"\n\n# set\ndata_nft.set_data(model_key, model_value, {\"from\": alice})\n```\n\n## 4. Retrieve value from data NFT\n\n```python\nmodel_value2 = data_nft.get_data(model_key)\nprint(f\"Found that {model_key} = {model_value2}\")\n```\n\nThis data is public, so anyone can retrieve it.\n\nThat's it! Note the simplicity. All data was stored and retrieved from on-chain. We don't need Ocean Provider or Ocean Aquarius for these use cases (though the latter can help for fast querying & retrieval).\n\nUnder the hood, it uses [ERC725](https://erc725alliance.org/), which augments ERC721 with a well-defined way to set and get key-value pairs.\n\n## 5. Next step\n\nThis README showed how to share _public_ key-value data. The next README covers private data. [Let's go there!](key-value-private.md).\n"
  },
  {
    "path": "READMEs/main-flow.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Main flow\n\nThis step is the fun one! In it, you'll publish a data asset, post for free / for sale, dispense it / buy it, and consume it.\n\nWe assume you've already (a) [installed Ocean](install.md), and (b) done [local setup](setup-local.md) or [remote setup](setup-remote.md). This flow works for either one, without any changes between them (!)\n\nSteps in the flow:\n1. Alice publishes dataset\n2. Bob gets access to the dataset (faucet, priced, etc)\n3. Bob consumes the dataset\n\nLet's go!\n\n## 1. Alice publishes dataset\n\nIn the same Python console:\n```python\n#data info\nname = \"Branin dataset\"\nurl = \"https://raw.githubusercontent.com/trentmc/branin/main/branin.arff\"\n\n#create data asset\n(data_nft, datatoken, ddo) = ocean.assets.create_url_asset(name, url, {\"from\": alice})\n\n#print\nprint(\"Just published asset:\")\nprint(f\"  data_nft: symbol={data_nft.symbol}, address={data_nft.address}\")\nprint(f\"  datatoken: symbol={datatoken.symbol}, address={datatoken.address}\")\nprint(f\"  did={ddo.did}\")\n```\n\nYou've now published an Ocean asset!\n- `data_nft` is the base (base IP)\n- `datatoken` for access by others (licensing)\n- `ddo` holding metadata\n\n\n(For more info, see [Appendix: Publish Details](#appendix-publish-details).)\n\n## 2. Bob gets access to the dataset\n\nBob wants to consume the dataset that Alice just published. The first step is for Bob to get 1.0 datatokens. Below, we show four possible approaches:\n- A & B are when Alice is in contact with Bob. She can mint directly to him, or mint to herself and transfer to him.\n- C is when Alice wants to share access for free, to anyone\n- D is when Alice wants to sell access\n\nIn the same Python console:\n```python\nfrom ocean_lib.ocean.util import to_wei\n\n#Approach A: Alice mints datatokens to Bob\ndatatoken.mint(bob, to_wei(1), {\"from\": alice})\n\n#Approach B: Alice mints for herself, and transfers to Bob\ndatatoken.mint(alice, to_wei(1), {\"from\": alice})\ndatatoken.transfer(bob, to_wei(1), {\"from\": alice})\n\n#Approach C: Alice posts for free, via a dispenser / faucet; Bob requests & gets\ndatatoken.create_dispenser({\"from\": alice})\ndatatoken.dispense(to_wei(1), {\"from\": bob})\n\n#Approach D: Alice posts for sale; Bob buys\n# D.1 Alice creates exchange\nprice = to_wei(100)\nexchange = datatoken.create_exchange({\"from\": alice}, price, ocean.OCEAN_address)\n\n# D.2 Alice makes 100 datatokens available on the exchange\ndatatoken.mint(alice, to_wei(100), {\"from\": alice})\ndatatoken.approve(exchange.address, to_wei(100), {\"from\": alice})\n\n# D.3 Bob lets exchange pull the OCEAN needed\nOCEAN_needed = exchange.BT_needed(to_wei(1), consume_market_fee=0)\nocean.OCEAN_token.approve(exchange.address, OCEAN_needed, {\"from\":bob})\n\n# D.4 Bob buys datatoken\nexchange.buy_DT(to_wei(1), consume_market_fee=0, tx_dict={\"from\": bob})\n````\n\n(For more info, see [Appendix: Dispenser / Faucet Details](#appendix-faucet-details) and [Exchange Details](#appendix-exchange-details).)\n\n## 3. Bob consumes the dataset\n\nBob now has the datatoken for the dataset! Time to download the dataset and use it.\n\nIn the same Python console:\n```python\n# Bob sends a datatoken to the service to get access\norder_tx_id = ocean.assets.pay_for_access_service(ddo, {\"from\": bob}).hex()\n\n# Bob downloads the file. If the connection breaks, Bob can try again\nasset_dir = ocean.assets.download_asset(ddo, bob, './', order_tx_id)\n\nimport os\nfile_name = os.path.join(asset_dir, \"file0\")\n```\n\nLet's check that the file is downloaded. In a new console:\n\n```console\ncd my_project/datafile.did:op:*\ncat file0\n```\nThe *beginning* of the file should contain the following contents:\n```\n% 1. Title: Branin Function\n% 3. Number of instances: 225\n% 6. Number of attributes: 2\n\n@relation branin\n\n@attribute 'x0' numeric\n@attribute 'x1' numeric\n@attribute 'y' numeric\n\n@data\n-5.0000,0.0000,308.1291\n-3.9286,0.0000,206.1783\n...\n```\n\n(For more info, see [Appendix: Consume Details](#appendix-consume-details).)\n\n## Next step\n\nYou've now gone through the main flow for Ocean, congrats!\n\nWhere you want to go next is up to you! Some possibilities:\n- Go back to the [top-level README](../README.md) to explore advanced flows like Compute-to-Data, Predict-ETH, and Data Farming\n- Review this README's appendices, which expand on the steps above with further flexibility.\n\n\n<h2 id=\"appendix-publish-details\">Appendix: Publish Details</h4>\n\n### Reconstructing Data NFT & Datatoken\n\nAnytime in the future, you can reconstruct your data NFT as an object in Python, via:\n\n```console\nfrom ocean_lib.models.data_nft import DataNFT\nconfig = <like shown elsewhere in READMEs>\ndata_nft_address = <what you wrote down previously>\ndata_nft = DataNFT(config, data_nft_address)\n```\n\nIt's similar for Datatokens. In Python:\n\n```console\nfrom ocean_lib.models.datatoken_base import DatatokenBase\nconfig = <like shown elsewhere in READMEs>\ndatatoken_address = <what you wrote down previously>\ndatatoken = DatatokenBase.get_typed(config, datatoken_address)\n```\n\n### Data NFT Interface\n\nData NFTs implement ERC721 functionality, ERC725 which extends it, and data management functionality on top.\n\nERC721:\n- Basic spec of a non-fungible token (NFT)\n- Official spec is at [erc721.org](https://erc721.org/)\n- Solidity interface: [IERC721Template.sol](https://github.com/oceanprotocol/contracts/blob/main/contracts/interfaces/IERC721Template.sol)\n\nERC725:\n- ERC725X is execution, and Y is key-value store\n- Official spec is at [eips.ethereum.org](https://eips.ethereum.org/EIPS/eip-725)\n- Solidity interface: [IERC725X.sol](https://github.com/oceanprotocol/contracts/blob/main/contracts/interfaces/IERC725X.sol) (execution) and [IERC725Y.sol](https://github.com/oceanprotocol/contracts/blob/main/contracts/interfaces/IERC725Y.sol) (key-value store)\n\nThe `data_nft` is a Python object of class [DataNFT](https://github.com/oceanprotocol/ocean.py/blob/main/ocean_lib/models/data_nft.py). The DataNFT class directly exposes the Solidity ERC721 & ERC725 interfaces. This means your `data_nft` object has a Python method for every Solidity method!\n\nBesides that, DataNFT implements other Python methods like `create_datatoken()` to improve developer experience. And, [ocean_assets.OceanAssets](https://github.com/oceanprotocol/ocean.py/blob/main/ocean_lib/ocean/ocean_assets.py) and other higher-level Python classes / methods work with DataNFT.\n\nOcean's architecture allows for >1 implementations of ERC721, each with its own Solidity template and Python class. Here are the templates:\n- [ERC721Template.sol](https://github.com/oceanprotocol/contracts/blob/main/contracts/templates/ERC721Template.sol), exposed as Python class `DataNFT`\n- (there's just one template so far; we can expect more in the future)\n\n### Datatoken Interface\n\nDatatokens implement ERC20 functionality, and data management functionality on top.\n\nERC20:\n- Basic spec of a fungible token standard\n- Official spec is at [eips.ethereum.org](https://eips.ethereum.org/EIPS/eip-20)\n- Solidity interface: [IERC20Template.sol](https://github.com/oceanprotocol/contracts/blob/main/contracts/interfaces/IERC20Template.sol)\n\nOcean's architecture allows for >1 implementations of ERC20, each with its own \"template\". Here are the templates so far (we can expect more over time).\n\nTemplate 1:\n- Solidity: [ERC20Template.sol](https://github.com/oceanprotocol/contracts/blob/main/contracts/templates/ERC20Template.sol)\n- Python wrapper: [Datatoken1](https://github.com/oceanprotocol/ocean.py/blob/main/ocean_lib/models/datatoken.py). It has a Python method for every Solidity method.\n- Implements methods like `start_order()`, `create_exchange()`, and `create_dispenser()` to enhance developer experience.\n\nTemplate 2:\n- Inherits from template 1 in both Solidity and Python.\n- Solidity: [ERC20TemplateEnterprise.sol](https://github.com/oceanprotocol/contracts/blob/main/contracts/templates/ERC20TemplateEnterprise.sol)\n- Python wrapper: [Datatoken2](https://github.com/oceanprotocol/ocean.py/blob/main/ocean_lib/models/datatoken2.py)\n- New method: [`buy_DT_and_order()`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/datatoken2.py#L20). This uses just 1 tx to do both actions at once (versus 2 txs for template 1).\n- New method: [`dispense_and_order()`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/datatoken2.py#L70). Similarly, uses just 1 tx.\n\nBelow you can find an explanatory table describing the template attributes:\n\nTemplate # | Class Label | Allows dispense by default? | Allows non-custody of datatokens? | Combines txs? | Allows non-custody of url?\n:----: | :----: | :----: | :----: | :----: | :----:\n1 | Datatoken1 | Y | N | N | N\n2 | Datatoken2 | N | Y | Y | N\n\n\n### DIDs and DDOs\n\nDDOs get returned in `create()` calls. Think of them as metadata, following a well-defined format.\n\nLet's get more specific. A [DID](https://w3c-ccg.github.io/did-spec/) is a decentralized identifier. A DID Document (DDO) is a JSON blob that holds information about the DID. Given a DID, a resolver will return the DDO of that DID.\n\nAn Ocean _asset_ has a DID and DDO, alongside a data NFT and >=0 datatokens. The DDO should include metadata about the asset, and define access in at least one service. Only owners or delegated users can modify the DDO.\n\nDDOs follow a schema - a pre-specified structure of possible metadata fields.\n\nOcean Aquarius helps in reading, decrypting, and searching through encrypted DDO data from the chain.\n\n[Ocean docs](https://docs.oceanprotocol.com/core-concepts/did-ddo) have further info yet.\n\n### Publish Flexibility\n\nHere's an example similar to the `create()` step above, but exposes more fine-grained control.\n\nIn the same python console:\n```python\n# Specify metadata and services, using the Branin test dataset\ndate_created = \"2021-12-28T10:55:11Z\"\nmetadata = {\n    \"created\": date_created,\n    \"updated\": date_created,\n    \"description\": \"Branin dataset\",\n    \"name\": \"Branin dataset\",\n    \"type\": \"dataset\",\n    \"author\": \"Trent\",\n    \"license\": \"CC0: PublicDomain\",\n}\n\n# Use \"UrlFile\" asset type. (There are other options)\nfrom ocean_lib.structures.file_objects import UrlFile\nurl_file = UrlFile(\n    url=\"https://raw.githubusercontent.com/trentmc/branin/main/branin.arff\"\n)\n\n# Publish data asset\nfrom ocean_lib.models.datatoken_base import DatatokenArguments\n_, _, ddo = ocean.assets.create(\n    metadata,\n    {\"from\": alice},\n    datatoken_args=[DatatokenArguments(files=[url_file])],\n)\n```\n\n\n### DDO Encryption or Compression\n\nThe DDO is stored on-chain. It's encrypted and compressed by default. Therefore it supports GDPR \"right-to-be-forgotten\" compliance rules by default.\n\nYou can control this during create():\n- To disable encryption, use `ocean.assets.create(..., encrypt_flag=False)`.\n- To disable compression, use `ocean.assets.create(..., compress_flag=False)`.\n- To disable both, use `ocean.assets.create(..., encrypt_flag=False, compress_flag=False)`.\n\n\n### Create _just_ a data NFT\n\nCalling `create()` like above generates a data NFT, a datatoken for that NFT, and a ddo. This is the most common case. However, sometimes you may want _just_ the data NFT, e.g. if using a data NFT as a simple key-value store. Here's how:\n```python\ndata_nft = ocean.data_nft_factory.create({\"from\": alice}, 'NFT1', 'NFT1')\n```\n\nIf you call `create()` after this, you can pass in an argument `data_nft_address:string` and it will use that NFT rather than creating a new one.\n\n### Create a datatoken from a data NFT\n\nCalling `create()` like above generates a data NFT, a datatoken for that NFT, and a ddo object. However, we may want a second datatoken. Or, we may have started with _just_ the data NFT, and want to add a datatoken to it. Here's how:\n\n```python\ndatatoken = data_nft.create_datatoken({\"from\": alice}, \"Datatoken 1\", \"DT1\")\n```\n\nIf you call `create()` after this, you can pass in an argument `deployed_datatokens:List[Datatoken1]` and it will use those datatokens during creation.\n\n<h2 id=\"appendix-faucet-details\">Appendix: Dispenser / Faucet Details</h4>\n\n\n### Dispenser Interface\n\nWe access dispenser (faucet) functionality from two complementary places: datatokens, and `Dispenser` object.\n\nA given datatoken can create exactly one dispenser for that datatoken.\n\n**Interface via datatokens:**\n- [`datatoken.create_dispenser()`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/datatoken.py#L337) - implemented in DatatokenBase, inherited by Datatoken2\n- [`datatoken.dispense()`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/datatoken.py#L380) - \"\"\n- [`datatoken.dispense_and_order()` - implemented in Datatoken1](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/datatoken.py#L439) and [in Datatoken2](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/datatoken_enterprise.py#L70). The latter only needs one tx to dispense and order.\n- [`datatoken.dispenser_status()`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/datatoken.py#L403) - returns a [`DispenserStatus`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/dispenser.py#L16) object\n\n**Interface via [`Dispenser`](https://github.com/oceanprotocol/ocean.py/blob/main/ocean_lib/models/dispenser.py) Python class:**\n- You can access its instance via `ocean.dispenser`.\n- `Dispenser` wraps [Dispenser.sol](https://github.com/oceanprotocol/contracts/blob/main/contracts/pools/dispenser/Dispenser.sol) Solidity implementation, to expose `Dispenser.sol`'s methods as Python methods.\n- Note that `Dispenser` is _global_ across all datatokens. Therefore calls to the Solidity contract - or Python calls that pass through - provide the datatoken address as an argument.\n- Example call: `ocean.dispenser.setAllowedSwapper(datatoken_addr, ZERO_ADDRESS, {\"from\": alice})`\n\n### Flexibility in Creating a Dispenser\n\n`datatoken.create_dispenser()` can take these optional arguments:\n- `max_tokens` - maximum number of tokens to dispense. The default is a large number.\n- `max_balance` - maximum balance of requester. The default is a large number.\n- `with_mint` - allow minting\n- `allowed_swapper` - swapper user address\n\nA call would look like `create_dispenser({\"from\": alice}, max_tokens=max_tokens, max_balance=max_balance)`.\n\n\n### Dispenser Status\n\nTo learn about dispenser status:\n\n```python\nstatus = datatoken.dispenser_status()\nprint(f\"For datatoken {datatoken.address}:\")\nprint(status)\n```\n\nIt will output something like:\n```text\nFor datatoken 0x92cA723B61CbD933390aA58b83e1F00cedf4ebb6:\nDispenserStatus:\n  active = True\n  owner_address = 0x1234\n  is_minter = True\n  max_tokens = 1000 (10000000000000000000000 wei)\n  max_balance = 10  (100000000000000000000 wei)\n  balance = 1\n  allowed_swapper = anyone can request\n```\n\n### Who can request tokens from a faucet\n\nTemplate 1 (`Datatoken1`):\n- Anyone can call `datatoken.dispense()` to request tokens.\n\nTemplate 2 (`Datatoken2`):\n- Option A. Anyone can `datatoken.dispense_and_order()` to request tokens, and order.\n- Option B. Not anyone can call `datatoken.dispense()` by default. To allow anyone, the publisher does: `ocean.dispenser.setAllowedSwapper(datatoken_address, ZERO_ADDRESS, {\"from\" : publisher_wallet})`, where `ZERO_ADDRESS` is `0x00..00`.\n\nDetails: `Dispenser.sol` has an attribute `allowed_swapper` to govern who can call `dispense()`. A value of `0x00...0` allows anyone. Template 1 has `ZERO_ADDRESS` as a default value; template 2 does not. However, template 2 allows anyone to call `dispense_and_order()`, independent of the value of `allowed_swapper`.\n\n<h2 id=\"appendix-exchange-details\">Appendix: Exchange Details</h4>\n\n### Exchange Interface\n\nWe access exchange functionality from three complementary places: datatokens, [`OneExchange`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/fixed_rate_exchange.py#L117) object, and (if needed) [`FixedRateExchange`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/fixed_rate_exchange.py#L106) object.\n\nA given datatoken can create one or more `OneExchange` objects.\n\n**Interface via datatokens:**\n- [`datatoken.create_exchange()`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/datatoken.py#L237) - Returns a `OneExchange` object.\n- [`datatoken.get_exchanges()`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/datatoken.py#L312) - Returns a list of `OneExchange` objects.\n\nOnce you've got a `OneExchange` object, most interactions are with it.\n\n**Interface via [`OneExchange`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/fixed_rate_exchange.py#L117) Python class:**\n- [`BT_needed()`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/fixed_rate_exchange.py#L150) - # basetokens (BTs) needed, to buy target # datatokens (DTs)\n- [`BT_received()`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/fixed_rate_exchange.py#L167) - # BTs you receive, in selling given # DTs\n- [`buy_DT()`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/fixed_rate_exchange.py#L184) - spend BTs to buy DTs\n- [`sell_DT()`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/fixed_rate_exchange.py#L230) - sell DTs, receive back BTs\n- and more, including get/set rate (price), toggle on/off, get/set fees, update balances\n\nWhile most interactions are with `OneExchange` described above, sometimes we may want to access the `FixedRateExchange` object.\n\n**Interface via [`FixedRateExchange`](https://github.com/oceanprotocol/ocean.py/blob/4aa12afd8a933d64bc2ed68d1e5359d0b9ae62f9/ocean_lib/models/fixed_rate_exchange.py#L106) Python class:**\n- You can access its instance via `ocean.fixed_rate_exchange`.\n- `FixedRateExchange` wraps [FixedRateExchange.sol](https://github.com/oceanprotocol/contracts/blob/main/contracts/pools/fixedRate/FixedRateExchange.sol) Solidity implementation, to expose `Dispenser.sol`'s methods as Python methods.\n- Note that `FixedRateExchange` is _global_ across all datatokens. Therefore calls to the Solidity contract - or Python calls that pass through - provide the exchange id address as an argument. It's exchange id, and not datatoken, because there may be >1 exchange for a given datatoken.\n- Example call: `ocean.fixed_rate_exchange.getRate(exchange_id)` returns rate (price).\n\n### Flexibility in Creating an Exchange\n\nWhen Alice posted the dataset for sale via `create_exchange()`, she used OCEAN. Alternatively, she could have used H2O, the OCEAN-backed stable asset. Or, she could have used USDC, DAI, RAI, WETH, or other, for a slightly higher fee (0.2% vs 0.1%).\n\n### Exchange Status\n\nHere's how to see all the exchanges that list the datatoken. In the Python console:\n```python\nexchanges = datatoken.get_exchanges() # list of OneExchange\n```\n\nTo learn more about the exchange status:\n\n```python\nprint(exchange.details)\nprint(exchange.exchange_fees_info)\n```\n\nIt will output something like:\n```text\n>>> print(exchange.details)\nExchangeDetails:\n  datatoken = 0xdA3cf7aE9b28E1A9B5F295201d9AcbEf14c43019\n  base_token = 0x24f42342C7C171a66f2B7feB5c712471bED92A97\n  fixed_rate (price) = 1.0 (1000000000000000000 wei)\n  active = True\n  dt_supply = 99.0 (99000000000000000000 wei)\n  bt_supply = 1.0 (1000000000000000000 wei)\n  dt_balance = 0.0 (0 wei)\n  bt_balance = 1.0 (1000000000000000000 wei)\n  with_mint = False\n  dt_decimals = 18\n  bt_decimals = 18\n  owner = 0x02354A1F160A3fd7ac8b02ee91F04104440B28E7\n\n>>> print(exchange.exchange_fees_info)\nExchangeFeeInfo:\n  publish_market_fee = 0.0 (0 wei)\n  publish_market_fee_available = 0.0 (0 wei)\n  publish_market_fee_collector = 0x02354A1F160A3fd7ac8b02ee91F04104440B28E7\n  opc_fee = 0.001 (1000000000000000 wei)\n  ocean_fee_available (to opc) = 0.001 (1000000000000000 wei)\n```\n\n\n<h2 id=\"appendix-create-bundled\">Create asset and pricing schema simultaneously </h2>\nOcean Assets allows you to bundle several common scenarios as a single transaction, thus lowering gas fees.\n\nAny of the `ocean.assets.create_<type>_asset()` functions can also take an optional parameter that describes a bundled pricing schema (Dispenser or Fixed Rate Exchange).\nThis can be either a DispenserArguments or an ExchangeArguments object. The parameters for these Arguments classes are identical to those for creating the object itself.\nE.g. adding `pricing_schema_args=DispenserArguments(to_wei(1), to_wei(1))` to the `create` function is equivalent to performing the creation and creating a dispenser later using `dt.create_dispenser(to_wei(1), to_wei(1))`.\n\nHere is an example involving an exchange:\n\n```python\nfrom ocean_lib.models.fixed_rate_exchange import ExchangeArguments\n(data_nft, datatoken, ddo) = ocean.assets.create_url_asset(\n    name,\n    url,\n    {\"from\": alice},\n    pricing_schema_args=ExchangeArguments(rate=to_wei(3), base_token_addr=ocean.OCEAN_address, dt_decimals=18)\n)\n\nassert len(datatoken.get_exchanges()) == 1\n```\n\n<h2 id=\"appendix-consume-details\">Appendix: Consume Details</h2>\n\n### Consume General\n\nTo \"consume\" an asset typically means placing an \"order\", where you pass in 1.0 datatokens and get back a url. Then, you typically download the asset from the url.\n\nFor more information, search for \"order\" in this README or related code.\n\n### About ARFF format\n\nThe file is in ARFF format, used by some AI/ML tools. Our example has two input variables (x0, x1) and one output.\n\n```console\n% 1. Title: Branin Function\n% 3. Number of instances: 225\n% 6. Number of attributes: 2\n\n@relation branin\n\n@attribute 'x0' numeric\n@attribute 'x1' numeric\n@attribute 'y' numeric\n\n@data\n-5.0000,0.0000,308.1291\n-3.9286,0.0000,206.1783\n...\n```\n\n<h2 id=\"appendix-consume-details\">Appendix: Ocean Instance</h2>\n\nAt the beginning of most flows, we create an `ocean` object, which is an instance of class [`Ocean`](https://github.com/oceanprotocol/ocean.py/blob/main/ocean_lib/ocean/ocean.py). It exposes useful information, including the following.\n\nConfig dict attribute:\n- `ocean.config_dict` or `ocean.config -> dict`\n\nOCEAN token:\n- `ocean.OCEAN_address -> str`\n- `ocean.OCEAN_token` or `ocean.OCEAN -> Datatoken`\n\nOcean smart contracts:\n- `ocean.data_nft_factory -> DataNFTFactoryContract`\n- `ocean.dispenser -> Dispenser` - faucets for free data\n- `ocean.fixed_rate_exchange -> FixedRateExchange` - exchanges for priced data\n\nSimple getters:\n- `ocean.get_nft_token(self, token_address: str) -> DataNFT`\n- `ocean.get_datatoken(self, token_address: str) -> Datatoken`\n- `ocean.def get_user_orders(self, address: str, datatoken: str)`\n- (and some others that are more complex)\n\nIt also provides Python wrappers to veOCEAN and Data Farming contracts. See [df.md](df.md) for details.\n"
  },
  {
    "path": "READMEs/parameters.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# On Config Parameters\n\nWe can set any config parameter using the config dictionary.\n\nAn `Ocean` instance will hold a `config_dict` that holds various config parameters. These parameters need to get set using the ExampleConfig class. This is set based on what's input to `Ocean` constructor:\n\n1.  dict input: `Ocean({'METADATA_CACHE_URI':..})`, in which case you have to build the web3 instance manually\n2.  use boilerplate from example config, which also sets the web3 instance to be used by each contract\n\n## Example\n\nHere is an example for (1): dict input, filled from envvars\n\n```python\nimport os\nfrom ocean_lib.ocean.ocean import Ocean\nfrom ocean_lib.example_config import get_web3\n\nnetwork_url = \"https://your-rpc.com\"\n\nd = {\n   'METADATA_CACHE_URI': \"https://v4.aquarius.oceanprotocol.com\",\n   'PROVIDER_URL' : \"https://v4.provider.goerli.oceanprotocol.com\",\n   \"web3_instance\": get_web3(network_url)\n}\nocean = Ocean(d)\n```\n\n## Further details\n\nFor the most precise description of config parameter logic, see the [Ocean() constructor implementation](https://github.com/oceanprotocol/ocean.py/blob/main/ocean_lib/ocean/ocean.py).\n"
  },
  {
    "path": "READMEs/predict-eth.md",
    "content": "This has been moved [here](https://github.com/oceanprotocol/predict-eth).\n"
  },
  {
    "path": "READMEs/profile-nfts-flow.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Quickstart: Profile NFTs\n\nThis is a flow showing how to do \"login with Web3\" with the help of Ocean data NFTs. In this flow, a dapp is not only connected to the user's wallet, but it can access profile data that the user has privately shared to it. Interestingly, these NFTs are also essentially [Soulbound Tokens](https://papers.ssrn.com/sol3/Delivery.cfm/SSRN_ID4105763_code1186331.pdf?abstractid=4105763&mirid=1) as well:)\n\nHere are the steps:\n\n1. Setup\n2. Alice publishes data NFT\n3. Alice adds key-value pair to data NFT. 'value' encrypted with a symmetric key 'symkey'\n4. Alice gets Dapp's public_key\n5. Alice encrypts symkey with Dapp's public key and shares to Dapp\n6. Dapp gets & decrypts symkey, then gets & decrypts original 'value'\n\n## 1. Setup\n\nEnsure that you've already (a) [installed Ocean](install.md), and (b) [set up locally](setup-local.md) or [remotely](setup-remote.md).\n\n## 2. Alice publishes data NFT\n\nWe publish a data NFT like elsewhere, except we set `transferable=False` (and skip print statements).\n\nIn the Python console:\n```python\n# Publish an NFT token. Note \"transferable=False\"\ndata_nft = ocean.data_nft_factory.create({\"from\": alice}, 'NFT1', 'NFT1', transferable=False)\n```\n\n## 3. Alice adds key-value pair to data NFT. 'value' encrypted with a symmetric key 'symkey'\n\n```python\n# imports\nfrom base64 import b64encode\nfrom cryptography.fernet import Fernet\nfrom eth_account.messages import encode_defunct\nfrom hashlib import sha256\nfrom ocean_lib.web3_internal.utils import sign_with_key\nfrom web3.main import Web3\n\n\n# Key-value pair\nprofiledata_name = \"fav_color\"\nprofiledata_val = \"blue\"\n\n# Prep key for setter. Contract/ERC725 requires keccak256 hash\nprofiledata_name_hash = Web3.keccak(text=profiledata_name)\n\n# Choose a symkey where:\n# - sharing it unlocks only this field: make unique to this data nft & field\n# - only Alice can compute it: make it a function of her private key\n# - is hardware wallet friendly: uses Alice's digital signature not private key\npreimage = data_nft.address + profiledata_name\npreimage = sha256(preimage.encode('utf-8')).hexdigest()\nprefix = \"\\x19Ethereum Signed Message:\\n32\"\nmsg = Web3.solidity_keccak(\n    [\"bytes\", \"bytes\"], [Web3.to_bytes(text=prefix), Web3.to_bytes(text=preimage)]\n)\nsigned = sign_with_key(msg, alice._private_key.hex())\nsymkey = b64encode(str(signed).encode('ascii'))[:43] + b'='  # bytes\n\n# Prep value for setter\nprofiledata_val_encr_hex = Fernet(symkey).encrypt(profiledata_val.encode('utf-8')).hex()\n\n# set\ndata_nft.setNewData(profiledata_name_hash, profiledata_val_encr_hex, {\"from\": alice})\n```\n\n## 4. Alice gets Dapp's public_key\n\nThere are various ways to compute public_key, and for Alice to get it (see Appendix). Here, the Dapp computes public_key from its private_key, then shares with Alice client-side within the script.\n\n```python\nfrom eth_keys import keys\nfrom eth_utils import decode_hex\n\ndapp_private_key = os.getenv('TEST_PRIVATE_KEY2')\ndapp_private_key_obj = keys.PrivateKey(decode_hex(dapp_private_key))\ndapp_public_key = str(dapp_private_key_obj.public_key)  # str\ndapp_address = dapp_private_key_obj.public_key.to_address()  # str\n```\n\n## 5. Alice encrypts symkey with Dapp's public key and shares to Dapp\n\nThere are various ways for Alice to share the encrypted symkey to the Dapp (see Appendix). Here, Alice writes a new key-value pair on the same data NFT. This approach allows the Dapp to access the info in future sessions without extra work.\n\n```python\nfrom ecies import encrypt as asymmetric_encrypt\n\nsymkey_name = (profiledata_name + ':for:' + dapp_address[:10])  # str\nsymkey_name_hash = Web3.keccak(text=symkey_name)\n\nsymkey_val_encr = asymmetric_encrypt(dapp_public_key, symkey)  # bytes\n\nsymkey_val_encr_hex = symkey_val_encr.hex()  # hex\n\n# arg types: key=bytes32, value=bytes, wallet=wallet\ndata_nft.setNewData(symkey_name_hash, symkey_val_encr_hex, {\"from\": alice})\n```\n\n## 6. Dapp gets & decrypts symkey, then gets & decrypts original 'value'\n\n```python\nfrom ecies import decrypt as asymmetric_decrypt\n\n# symkey_name_hash = <Dapp would set like above>\nsymkey_val_encr2 = data_nft.getData(symkey_name_hash)\nsymkey2 = asymmetric_decrypt(dapp_private_key, symkey_val_encr2)\n\n# profiledata_name_hash = <Dapp would set like above>\nprofiledata_val_encr_hex2 = data_nft.getData(profiledata_name_hash)\nprofiledata_val2_bytes = Fernet(symkey).decrypt(profiledata_val_encr_hex2)\nprofiledata_val2 = profiledata_val2_bytes.decode('utf-8')\n\nprint(f\"Dapp found profiledata {profiledata_name} = {profiledata_val2}\")\n```\n\n\n## Appendix\n\nStep 4 gave one way for Alice to get the Dapp's public key; step 5 gave one way for the Dapp to get the encrypted symkey. Here are more options.\n\nOn computing public keys:\n- If you have the private_key, you can compute the public_key (used above)\n- Hardware wallets don't expose private_keys. And, while they do expose a _root_ public_key, you shouldn't publicly share those because it lets anyone see all your wallets\n- However, you _can_ compute anyone's public_key from any tx. This is a general solution. Conveniently, Etherscan shows it too.\n\nPossible ways for Alice to get Dapp's public key:\n- Alice auto-computes from any of Dapp's previous txs.\n- Alice retrieves it from a public-ish registry or api, e.g. etherscan\n- Dapp computes it from private_key or from past tx, then shares.\n\nPossible ways for Alice to share an encrypted symkey, or for Dapp to share public_key:\n- Directly client-side\n  - Client-side: in a browser with Metamask - [example by FELToken](https://betterprogramming.pub/exchanging-encrypted-data-on-blockchain-using-metamask-a2e65a9a896c). This is a good choice because it does no on-chain txs.\n  - Client-side: in a script. Like done in step 4 above for public key\n- Over a public channel:\n  - Public channel: write a new key-value pair on the same data NFT. Like done in step 5 above for encrypted symkey. This is a good choice because the Dapp can access the info in future sessions without extra work.\n  - Public channel: a new data NFT for each message\n  - Public channel: traditional: http, email, or any messaging medium\n\n\n"
  },
  {
    "path": "READMEs/publish-flow-credentials.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Quickstart: Publish & Metadata update Flows for credentials\n\nThis quickstart describes how to use credentials in order to limit access to a dataset.\n\nEnsure that you've already (a) [installed Ocean](install.md), and (b) [set up locally](setup-local.md) or [remotely](setup-remote.md).\n\nHere are the steps:\n\n1.  Publish a dataset that can only be accessed by Alice and Bob. Everyone else will be denied.\n2.  Update the dataset so only Bob will be denied, everyone else will have access.\n\n\nLet's go through each step.\n\n## 2. Carlos publishes the API asset, allowing only Alice and Bob as consumers\n\n\n```python\nurl = 'http://www.example.net'\nname = \"Restricted dataset\"\ncredentials = {\n    \"allow\": [{\"type\": \"address\", \"values\": [alice.address, bob.address]}],\n    \"deny\": [],\n}\n#create asset\n(data_nft, datatoken, ddo) = ocean.assets.create_url_asset(name, url, {\"from\": carlos}, credentials = credentials)\nprint(f\"Just published asset, with did={ddo.did}\")\n```\n\n\nThat's it! You've created a data asset which is accesible only to Alice and Bob. Consume here is just like in [consume-flow](consume-flow.md).\n\n\n## 2. Carlos updates the asset, allowing everyone, but denying Bob\n\nUsing the ddo directly, or later using `ddo=ocean.assets.resolve(<DID you wrote down previously>)`\n\n```python\nddo.credentials = {\n    \"allow\": [],\n    \"deny\": [{\"type\": \"address\", \"values\": [bob.address]}],\n}\nddo = ocean.assets.update(ddo, {\"from\": carlos})\n```\n\n\nThat's it! Now everyone can access the dataset, except Bob. Consume here is just like in [consume-flow](consume-flow.md).\n\nFor more information about credentials, you can refer to [docs](https://docs.oceanprotocol.com/core-concepts/did-ddo#credentials).\n"
  },
  {
    "path": "READMEs/publish-flow-graphql.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Quickstart: Publish & Consume Flow for GraphQL data type\n\nThis quickstart describes a flow to publish & consume GraphQL-style URIs. In our example, the data asset is a query to find data NFTs via ocean-subgraph.\n\nHere are the steps:\n\n1.  Setup\n2.  Publish dataset\n3.  Consume dataset\n\nLet's go through each step.\n\n## 1. Setup\n\nEnsure that you've already (a) [installed Ocean](install.md), and (b) [set up locally](setup-local.md) or [remotely](setup-remote.md).\n\n## 2. Publish dataset\n\nIn the same Python console:\n```python\n#data info\nname = \"Data NFTs in Ocean\"\nurl=\"https://v4.subgraph.goerli.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph\"\nquery=\"\"\"query{\n               nfts(orderBy: createdTimestamp,orderDirection:desc){\n                    id\n                    symbol\n                    createdTimestamp\n                    }\n               }\n\"\"\"\n\n#create asset\n(data_nft, datatoken, ddo) = ocean.assets.create_graphql_asset(name, url, query, {\"from\": alice})\nprint(f\"Just published asset, with did={ddo.did}\")\n```\n\nThat's it! You've created a data asset of \"GraphqlQuery\" asset type. It includes a data NFT, a datatoken for the data NFT, and metadata.\n\n## 3.  Consume dataset\n\nConsume here is just like in [consume-flow](consume-flow.md). The file downloaded is a .json. From that, use the python `json` library to parse it as desired.\n\n"
  },
  {
    "path": "READMEs/publish-flow-onchain.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Quickstart: Publish & Consume Flow using onchain data source\n\nThis quickstart describes a flow to publish & consume onchain data source\n\nHere are the steps:\n\n1.  Setup\n2.  Publish dataset\n3.  Consume dataset\n\nLet's go through each step.\n\n## 1. Setup\n\nEnsure that you've already (a) [installed Ocean](install.md), and (b) [set up locally](setup-local.md) or [remotely](setup-remote.md).\n\n## 2. Publish dataset\n\nIn the same Python console:\n```python\n#data info\nfrom ocean_lib.ocean.util import get_address_of_type\n\nname = \"swapOceanFee function call\"\ncontract_address = get_address_of_type(config, \"Router\")\ncontract_abi = {\n                \"inputs\": [],\n                \"name\": \"swapOceanFee\",\n                \"outputs\": [{\"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\"}],\n                \"stateMutability\": \"view\",\n                \"type\": \"function\",\n\t\t}\n\n#create asset\n(data_nft, datatoken, ddo) = ocean.assets.create_onchain_asset(name, contract_address, contract_abi, {\"from\": alice})\nprint(f\"Just published asset, with did={ddo.did}\")\n```\n\nThat's it! You've created a data asset of \"SmartContractCall\" asset type. It includes a data NFT, a datatoken for the data NFT, and metadata.\n\n## 3.  Consume the dataset\n\n(Consume here is just like in [consume-flow](READMEs/consume-flow.md]. The file downloaded is a .json. From that, use the python `json` library to parse it as desired.)\n"
  },
  {
    "path": "READMEs/publish-flow-restapi.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Quickstart: Publish & Consume Flow for REST API-style URIs\n\nThis quickstart describes a flow to publish Kraken REST API of OCEAN-USD pair price feed, to make it available as free data asset on Ocean, and to consume it.\n\nHere are the steps:\n\n1.  Setup\n2.  Alice publishes the API asset\n3.  Alice creates a faucet for the asset\n4.  Bob gets a free datatoken, then consumes it\n\nLet's go through each step.\n\n## 1. Setup\n\nEnsure that you've already (a) [installed Ocean](install.md), and (b) [set up locally](setup-local.md) or [remotely](setup-remote.md).\n\n## 2. Alice publishes the API asset\n\nIn the same Python console:\n```python\n# Data info\nname = \"Kraken API OCEAN-USD price feed\"\npair = 'OCEANUSD' # Choose the trading pair\ninterval = '1440' # Choose the time interval in minutes (1440 for daily)\n\nfrom datetime import datetime, timedelta\nend_datetime = datetime.now()\nstart_datetime = end_datetime - timedelta(days=7) # The previous week\nsince = int(start_datetime.timestamp() * 1000) # Choose the start time in Unix timestamp\nurl = f'https://api.kraken.com/0/public/OHLC?pair={pair}&interval={interval}&since={since}'\n\n#create asset\n(data_nft, datatoken, ddo) = ocean.assets.create_url_asset(name, url, {\"from\": alice})\nprint(f\"Just published asset, with did={ddo.did}\")\n```\n\n### 3. Alice creates a faucet for the asset\n\nIn the same Python console:\n```python\ndatatoken.create_dispenser({\"from\": alice})\n```\n\n### 4. Bob gets a free datatoken, then consumes it\n\nNow, you're Bob. First, download the file.\n\nIn the same Python console:\n```python\n# Set asset did. Practically, you'd get this from Ocean Market. _This_ example uses prior info.\nddo_did = ddo.did\n\n# Bob gets a free datatoken, sends it to the service, and downloads\ndatatoken.dispense(to_wei(1), {\"from\": bob})\norder_tx_id = ocean.assets.pay_for_access_service(ddo, {\"from\": bob}).hex()\nasset_dir = ocean.assets.download_asset(ddo, bob, './', order_tx_id)\n\nimport os\nfile_name = os.path.join(asset_dir, 'file0')\n```\n\nNow, load the file and use its data.\n\nThe data follows the Kraken docs specs for Data, [here](https://docs.kraken.com/rest/#tag/Market-Data/operation/getOHLCData).\n\nIn the same Python console:\n```python\n\n# Load from file into memory\nwith open(file_name, \"r\") as file:\n    # Data is a string with the result inside.\n    data_str = file.read().rstrip().replace(\"'\", '\"')\n\nimport json\ndata = json.loads(data_str)\n\n# Data is a list of lists\n# -Outer dictionary contains 2 keys, one for errors and one for the result with the pair.\n# -Inner dictionary have 9 entries each: Kline open time, Open price, High price, Low price, close Price, Vol, ..\n# Get close price\nclose_prices = [float(data_at_day[4]) for data_at_day in data['result'][pair]]\nprint(f\"close prices: {close_prices}\")\n```\n\n"
  },
  {
    "path": "READMEs/release-process.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# The ocean.py Release Process\n\n## Step 0: Update documentation\n\n- If your changes affect what docs.oceanprotocol.com shows, then make changes in the docs repo https://github.com/oceanprotocol/docs and change\n\n## Step 1: Bump version and push changes\n\n- Identify the current version. It's listed at [pypi.org/project/ocean-lib](https://pypi.org/project/ocean-lib/), in this repo in [.bumpversion.cfg](../.bumpversion.cfg), and elsewhere.\n\n- Create a new local feature branch, e.g. `git checkout -b feature/bumpversion-to-v1.2.5`\n\n- Ensure you're in virtual env: `source venv/bin/activate`\n\n- Run `./bumpversion.sh` to bump the project version, as follows:\n\n  - To bump the major version (v**X**.Y.Z): `./bumpversion.sh major`\n  - To bump the minor version (vX.**Y**.Z): `./bumpversion.sh minor`\n  - To bump the patch version (vX.Y.**Z**): `./bumpversion.sh patch`\n  - (Ocean.py follows [semantic versioning](https://semver.org/).)\n\n- Commit the changes to the feature branch. For example:\n\n  `git commit -m \"Bump version v1.2.4 -> v1.2.5\"`\n\n- Push the feature branch to GitHub.\n\n  `git push origin feature/bumpversion-to-v1.2.5`\n\n## Step 2: Merge changes to main branch\n\n- Make a pull request from the just-pushed branch.\n\n- Wait for all the tests to pass!\n\n- Merge the pull request into the `main` branch.\n\n## Step 3: Release\n\n- To make a GitHub release (which creates a Git tag):\n\n  - Go to the ocean.py repo's Releases page <https://github.com/oceanprotocol/ocean.py/releases>\n  - Click \"Draft a new release\".\n  - For tag version, put something like `v1.2.5`\n  - For release title, put the same value (like `v1.2.5`).\n  - For the target, select the `main` branch, or the just-merged commit.\n  - Describe the main changes. (In the future, these will come from the changelog.)\n  - Click \"Publish release\".\n\n## Step 4: Verify\n\n- GitHub Actions will detect the release (a new tag) and run the deployment and publishing to PyPi.\n\n- Check PyPI for the new release at <https://pypi.org/project/ocean-lib/>\n\n"
  },
  {
    "path": "READMEs/search-and-filter-assets.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n\n# Quickstart: Search Assets Flow\n\nThis quickstart describes how assets can be found by their `tags` from Aquarius.\n\n\n## 1. Setup\n\nEnsure that you've already (a) [installed Ocean](install.md), and (b) [set up locally](setup-local.md) or [remotely](setup-remote.md).\n\n## 2. Alice publishes datasets\n\nNow, you're Alice.\n\n```python\n#data info\nurl = \"https://raw.githubusercontent.com/trentmc/branin/main/branin.arff\"\n\n# Created a list of tags for the following assets\ntags = [\n    [\"Branin dataset 1\", \"test\", \"ganache\", \"best asset\"],\n    [\"Branin dataset 2\", \"test\", \"ocean\"],\n    [\"Branin dataset 3\", \"AI\", \"dataset\", \"testing\"],\n]\n# Publish few assets for testing\nfor tag in tags:\n    name = tag[0]\n    tx_dict = {\"from\": alice}\n    metadata = ocean.assets.__class__.default_metadata(name, tx_dict)\n    metadata.update({\"tags\": tag[1:]})\n    (data_NFT, datatoken, ddo) = ocean.assets.create_url_asset(name, url, tx_dict, metadata=metadata)\n    print(f\"Just published asset, with did={ddo.did}\")\n```\n## 3. Alice filters assets by their `tags`\n\nAlice can filter the assets by a certain tag and after can retrieve the necessary\ninformation afterwards.\n\n```python\n# Get a list of assets filtered by a given tag.\n# All assets that contain the specified tag name\ntag = \"test\"\nall_ddos = ocean.assets.search(tag)\n\n# Filter just by the `tags` key\nfiltered_ddos = list(\n    filter(\n        lambda a: tag in a.metadata[\"tags\"],\n        list(filter(lambda a: \"tags\" in a.metadata.keys(), all_ddos)),\n    )\n)\n\n# Make sure that the provided tag is valid.\nassert len(filtered_ddos) > 0, \"Assets not found with this tag.\"\n\n# Retrieve the wanted information from assets.\nfor ddo in filtered_ddos:\n    print(f\"ddo.did :{ddo.did}\")\n    print(f\"ddo.metadata :{ddo.metadata}\")\n    print(f\"ddo.nft :{ddo.nft}\")\n    print(f\"ddo.datatokens :{ddo.datatokens}\")\n```\n\n## Running custom queries\nYou can run any custom ES query using OceanAssets. For example:\n```python\nresults = ocean.assets.query(\n    {\n        \"query\": {\n            \"query_string\": {\n                \"query\": \"Branin dataset 1\",\n                \"fields\": [\"metadata.name\"],\n            }\n        }\n    }\n)\nassert results[0].metadata[\"name\"] == \"Branin dataset 1\"\n```\n"
  },
  {
    "path": "READMEs/services.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# About Ocean off-chain services\n\n## Introduction\n\nOcean uses these off-chain services:\n\n-   [Ocean Provider](https://github.com/oceanprotocol/provider) is for data services. Specifically, it's a REST API serving requests for two types of data services: static urls (for downloading data) and compute services. It's run by the marketplace or the data publisher.\n-   [Ocean Aquarius](https://github.com/oceanprotocol/aquarius) is metadata cache REST API. This helps to aid search in marketplaces.\n\nWe now describe how to use these, for each of:\n\n- Local Services: Default\n- Local Services: Non-Default\n- Remote Services: Default\n- Remote Services: Non-Default\n\n### Local Services: Default\n\nWhen you follow [local setup](READMEs/setup-local.md), you will use Barge. Barge runs its own Ganache, and also its own Provider and Aquarius. You don't need to do more.\n\n### Local Services: Non-Default\n\nInstead of pointing to existing services (in Barge), you can run your own. Here's how.\n\nOpen a new console, and get provider running:\n\n```console\ndocker run oceanprotocol/provider:latest\n```\n\nOpen another new console, and get aquarius running:\n\n```console\ndocker run oceanprotocol/aquarius:latest\n```\n\nHere are the urls for the local services, for use in the config dict.\n\n-   Provider url: `http://127.0.0.1:8030`\n-   Aquarius url: `http://127.0.0.1:5000`\n\nRemember, here's how the config dict is set.\n```python\nfrom ocean_lib.example_config import get_config_dict\nconfig = get_config_dict(<RPC_URL>) # returns a dict\n# (then, here you can update the config dict as you wish)\nocean = Ocean(config)\n```\n\n### Remote Services: Default\n\nFor convenience, Ocean Protocol Foundation (OPF) runs an instance of Provider, and of Aquarius. [Ocean network docs](https://docs.oceanprotocol.com/core-concepts/networks) gives the urls.\n\nWhen you follow [remote setup](READMEs/setup-remote.md), it will default to use these OPF-run Provider and Aquarius. You don't need to do more.\n\n\n### Remote Services: Non-Default\n\nYou can run your own Provider or Aquarius, like shown above. And then point to it from your config dict.\n\nYou can also point to a Provider or Aquarius instance run by a 3rd party. Simply point to it from your config dict.\n"
  },
  {
    "path": "READMEs/setup-local.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Local Setup\n\nHere, we do setup for local testing.\n\nWe assume you've already [installed Ocean](install.md).\n\n## 1. Download barge and run services\n\nOcean `barge` runs ganache (local blockchain), Provider (data service), and Aquarius (metadata cache).\n\nBarge helps you quickly become familiar with Ocean, because the local blockchain has low latency and no transaction fees. Accordingly, many READMEs use it. However, if you plan to only use Ocean with remote services, you don't need barge.\n\nNote: if you are running MacOS or Windows, we recommend to go directly to [Remote Setup](setup-remote.md). Why: Barge uses Docker, which behaves badly on MacOS and Windows. We're working to address this [here](https://github.com/oceanprotocol/ocean.py/issues/1313).\n\nIn a new console:\n\n```console\n# Grab repo\ngit clone https://github.com/oceanprotocol/barge\ncd barge\n\n# Clean up old containers (to be sure)\ndocker system prune -a --volumes\n\n# Run barge: start Ganache, Provider, Aquarius; deploy contracts; update ~/.ocean\n# for support of type 2 transactions\nexport GANACHE_FORK=london\n./start_ocean.sh\n```\n\nNow that we have barge running, we can mostly ignore its console while it runs.\n\n## 2. Set envvars\n\nFrom here on, go to a console different than Barge. (E.g. the console where you installed Ocean, or a new one.)\n\nFirst, ensure that you're in the working directory, with venv activated:\n\n```console\ncd my_project\nsource venv/bin/activate\n```\n\nThen, set keys in readmes. As a Linux user, you'll use \"`export`\". In the same console:\n\n```console\n# keys for alice and bob in readmes\nexport TEST_PRIVATE_KEY1=0x8467415bb2ba7c91084d932276214b11a3dd9bdb2930fefa194b666dd8020b99\nexport TEST_PRIVATE_KEY2=0x1d751ded5a32226054cd2e71261039b65afb9ee1c746d055dd699b1150a5befc\nexport TEST_PRIVATE_KEY3=0x732fbb7c355aa8898f4cff92fa7a6a947339eaf026a08a51f171199e35a18ae0\n\n\n# key for minting fake OCEAN\nexport FACTORY_DEPLOYER_PRIVATE_KEY=0xc594c6e5def4bab63ac29eed19a134c130388f74f019bc74b8f4389df2837a58\n```\n\n## 3. Setup in Python\n\nIn the same console, run Python console:\n```console\npython\n```\n\nIn the Python console:\n```python\n# Create Ocean instance\nfrom ocean_lib.example_config import get_config_dict\nconfig = get_config_dict(\"http://localhost:8545\")\n\nfrom ocean_lib.ocean.ocean import Ocean\nocean = Ocean(config)\n\n# Create OCEAN object. Barge auto-created OCEAN, and ocean instance knows\nOCEAN = ocean.OCEAN_token\n\n# Mint fake OCEAN to Alice & Bob\nfrom ocean_lib.ocean.mint_fake_ocean import mint_fake_OCEAN\nmint_fake_OCEAN(config)\n\n# Create Alice's wallet\nimport os\nfrom eth_account import Account\n\nalice_private_key = os.getenv(\"TEST_PRIVATE_KEY1\")\nalice = Account.from_key(private_key=alice_private_key)\nassert ocean.wallet_balance(alice) > 0, \"Alice needs ETH\"\nassert OCEAN.balanceOf(alice) > 0, \"Alice needs OCEAN\"\n\n# Create additional wallets. While some flows just use Alice wallet, it's simpler to do all here.\nbob_private_key = os.getenv('TEST_PRIVATE_KEY2')\nbob = Account.from_key(private_key=bob_private_key)\nassert ocean.wallet_balance(bob) > 0, \"Bob needs ETH\"\nassert OCEAN.balanceOf(bob) > 0, \"Bob needs OCEAN\"\n\ncarlos_private_key = os.getenv('TEST_PRIVATE_KEY3')\ncarlos = Account.from_key(private_key=carlos_private_key)\nassert ocean.wallet_balance(carlos) > 0, \"Carlos needs ETH\"\nassert OCEAN.balanceOf(carlos) > 0, \"Carlos needs OCEAN\"\n\n\n# Compact wei <> eth conversion\nfrom ocean_lib.ocean.util import to_wei, from_wei\n```\n\n## 4. Next step\n\nYou've now set up everything you need for local testing, congrats!\n\nThe next step - the fun one - is to walk through the [main flow](main-flow.md). In it, you'll publish a data asset, post for free / for sale, dispense it / buy it, and consume it.\n\nBecause you've set up for local, you'll be doing all these steps on the local network.\n"
  },
  {
    "path": "READMEs/setup-remote.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Remote Setup\n\nHere, we do setup for Mumbai, the testnet for Polygon. It's similar for other remote chains.\n\nWe assume you've already [installed Ocean](install.md).\n\nHere, we will:\n1. Configure networks\n2. Create two accounts - `REMOTE_TEST_PRIVATE_KEY1` and `2`\n3. Get fake MATIC on Mumbai\n4. Get fake OCEAN on Mumbai\n5. Set envvars\n6. Set up Alice and Bob wallets in Python\n\nLet's go!\n\n## 1. Configure Networks\n\n### 1.1 Setup network RPC URLs for all desired networks\n\nAll [Ocean chain deployments](https://docs.oceanprotocol.com/discover/networks) (Eth mainnet, Polygon, etc) are supported.\n\n## 2. Create EVM Accounts (One-Time)\n\nAn EVM account is singularly defined by its private key. Its address is a function of that key. Let's generate two accounts!\n\nIn a new or existing console, run Python.\n```console\npython\n```\n\nIn the Python console:\n\n```python\nfrom eth_account.account import Account\naccount1 = Account.create()\naccount2 = Account.create()\n\nprint(f\"\"\"\nREMOTE_TEST_PRIVATE_KEY1={account1.key.hex()}, ADDRESS1={account1.address}\nREMOTE_TEST_PRIVATE_KEY2={account2.key.hex()}, ADDRESS2={account2.address}\n\"\"\")\n```\n\nThen, hit Ctrl-C to exit the Python console.\n\nNow, you have two EVM accounts (address & private key). Save them somewhere safe, like a local file or a password manager.\n\nThese accounts will work on any EVM-based chain: production chains like Eth mainnet and Polygon, and testnets like Goerli and Mumbai. Here, we'll use them for Mumbai.\n\n\n## 3. Get (fake) MATIC on Mumbai\n\nWe need the a network's native token to pay for transactions on the network. [ETH](https://ethereum.org/en/get-eth/) is the native token for Ethereum mainnet; [MATIC](https://polygon.technology/matic-token/) is the native token for Polygon, and [(fake) MATIC](https://faucet.polygon.technology/) is the native token for Mumbai.\n\nTo get free (fake) MATIC on Mumbai:\n1. Go to the faucet https://faucet.polygon.technology/. Ensure you've selected \"Mumbai\" network and \"MATIC\" token.\n2. Request funds for ADDRESS1\n3. Request funds for ADDRESS2\n\nYou can confirm receiving funds by going to the following url, and seeing your reported MATIC balance: `https://mumbai.polygonscan.com/address/<ADDRESS1 or ADDRESS2>`\n\n## 4. Get (fake) OCEAN on Mumbai\n\n[OCEAN](https://oceanprotocol.com/token) can be used as a data payment token, and locked into veOCEAN for Data Farming / curation. The READMEs show how to use OCEAN in both cases.\n- OCEAN is an ERC20 token with a finite supply, rooted in Ethereum mainnet at address [`0x967da4048cD07aB37855c090aAF366e4ce1b9F48`](https://etherscan.io/token/0x967da4048cD07aB37855c090aAF366e4ce1b9F48).\n- OCEAN on other production chains derives from the Ethereum mainnet OCEAN. OCEAN on Polygon (mOCEAN) is at [`0x282d8efce846a88b159800bd4130ad77443fa1a1`](https://polygonscan.com/token/0x282d8efce846a88b159800bd4130ad77443fa1a1).\n- (Fake) OCEAN is on each testnet. Fake OCEAN on Mumbai is at [`0xd8992Ed72C445c35Cb4A2be468568Ed1079357c8`](https://mumbai.polygonscan.com/token/0xd8992Ed72C445c35Cb4A2be468568Ed1079357c8).\n\nTo get free (fake) OCEAN on Mumbai:\n1. Go to the faucet https://faucet.mumbai.oceanprotocol.com/\n2. Request funds for ADDRESS1\n3. Request funds for ADDRESS2\n\nYou can confirm receiving funds by going to the following url, and seeing your reported OCEAN balance: `https://mumbai.polygonscan.com/token/0xd8992Ed72C445c35Cb4A2be468568Ed1079357c8?a=<ADDRESS1 or ADDRESS2>`\n\n## 5. Set envvars\n\nAs usual, Linux/MacOS needs \"`export`\" and Windows needs \"`set`\". In the console:\n\n#### Linux & MacOS users:\n```console\n# For accounts: set private keys\nexport REMOTE_TEST_PRIVATE_KEY1=<your REMOTE_TEST_PRIVATE_KEY1>\nexport REMOTE_TEST_PRIVATE_KEY2=<your REMOTE_TEST_PRIVATE_KEY2>\n\nexport MUMBAI_RPC_URL=<your RPC_URL> # exported used for convenience/security, you can also use the direct URL string later\n```\n\n\n#### Windows users:\n```console\n# For accounts: set private keys\nset REMOTE_TEST_PRIVATE_KEY1=<your REMOTE_TEST_PRIVATE_KEY1>\nset REMOTE_TEST_PRIVATE_KEY2=<your REMOTE_TEST_PRIVATE_KEY2>\n\nset MUMBAI_RPC_URL=<your RPC_URL> # exported used for convenience/security, you can also use the direct URL string later\n```\n\nOptionally, chainlist.org has other RPCs for [Mumbai](https://chainlist.org/chain/80001) and [Polygon](https://chainlist.org/chain/137).\n\n## 6. Setup in Python\n\nIn your working console, run Python:\n```console\npython\n```\n\nIn the Python console:\n```python\n# Create Ocean instance\nimport os\nfrom ocean_lib.example_config import get_config_dict\nfrom ocean_lib.ocean.ocean import Ocean\nconfig = get_config_dict(os.getenv(\"MUMBAI_RPC_URL\"))  # you can also input the string directly\nocean = Ocean(config)\n\n# Create OCEAN object. ocean_lib knows where OCEAN is on all remote networks\nOCEAN = ocean.OCEAN_token\n\n# Create Alice's wallet\nfrom eth_account import Account\n\nalice_private_key = os.getenv('REMOTE_TEST_PRIVATE_KEY1')\nalice = Account.from_key(private_key=alice_private_key)\nassert ocean.wallet_balance(alice) > 0, \"Alice needs MATIC\"\nassert OCEAN.balanceOf(alice) > 0, \"Alice needs OCEAN\"\n\n# Create Bob's wallet. While some flows just use Alice wallet, it's simpler to do all here.\nbob_private_key = os.getenv('REMOTE_TEST_PRIVATE_KEY2')\nbob = Account.from_key(private_key=bob_private_key)\nassert ocean.wallet_balance(bob) > 0, \"Bob needs MATIC\"\nassert OCEAN.balanceOf(bob) > 0, \"Bob needs OCEAN\"\n\n# Compact wei <> eth conversion\nfrom ocean_lib.ocean.util import to_wei, from_wei\n```\n\nIf you get a gas-related error like `transaction underpriced`, you'll need to change the `priority_fee` or `max_fee`.\n\n## Next step\n\nYou've now set up everything you need for testing on a remote chain, congrats! it's similar for any remote chain.\n\nThe next step is to walk through the [main flow](main-flow.md). In it, you'll publish a data asset, post for free / for sale, dispense it / buy it, and consume it.\n\nBecause you've set up for remote, you'll be doing all these steps on the remote network.\n"
  },
  {
    "path": "READMEs/using-clef.md",
    "content": "<!--\nCopyright 2023 Ocean Protocol Foundation\nSPDX-License-Identifier: Apache-2.0\n-->\n\n# Using hardware wallets with ocean.py\n\nThis README describes how to setup ocean.py with hardware wallets.\n\nWe assume you've already (a) [installed Ocean](install.md), configured any environment variables necessary and created the Ocean object as described in (b) done [local setup](setup-local.md) or [remote setup](setup-remote.md).\nThese instructions are applicable to both local and remote setup. If you intend to use hardware wallets ONLY, then you can skip the wallet creation parts in the setup instructions.\n\n## 1. Setting up and running Clef\nocean.py allows the use of hardware wallets via [Clef](https://geth.ethereum.org/docs/clef/tutorial), an account management tool included within [Geth](https://geth.ethereum.org/)\n\nTo use a hardware wallet with ocean.py, start by [installing Geth](https://geth.ethereum.org/docs/install-and-build/installing-geth).\nOnce finished, type the following command in a bash console and follow the on-screen prompts to set of Clef:\n\n```console\nclef init\n```\n\nIf you need to create a new account, you can use the command `clef newaccount`. For other usefull commands, please consult the [Clef documentation](https://geth.ethereum.org/docs/tools/clef/introduction).\n\nOnce Clef is configured, run it in a bash console as needed, i.e.\n\n```console\n# you can use a different chain if needed\nclef --chainid 8996\n```\n\nYou can also customise your run, e.g. `clef --chainid 8996 --advanced`.\n\nKeep the clef console open, you will be required to approve transactions and input your password when so requested.\n\n## 2. Connect ocean.py to Clef via Brownie\n\nIn your Python console where you have setup the Ocean object:\n\n```python\nfrom ocean_lib.web3_internal.clef import get_clef_accounts\nclef_accounts = get_clef_accounts()\n```\n\nApprove the connection from the Clef console. This will add your Clef account to the `accounts` array.\nYou can now use the Clef account instead of any wallet argument, e.g. when publishing or consuming DDOs.\n\n\n```python\n# pick up the account for convenience\nclef_account = clef_accounts[index]\n\n# make sure account is funded. Let's transfer some ether and OCEAN from alice\nfrom ocean_lib.ocean.util import send_ether\nsend_ether(config, alice, clef_account.address, to_wei(4))\nOCEAN.transfer(clef_account, to_wei(4), {\"from\": alice})\n\n# publish and download an asset\nname = \"Branin dataset\"\nurl = \"https://raw.githubusercontent.com/trentmc/branin/main/branin.arff\"\n\n(data_nft, datatoken, ddo) = ocean.assets.create_url_asset(name, url, {\"from\": clef_account})\ndatatoken.mint(clef_account, to_wei(1), {\"from\": clef_account})\norder_tx_id = ocean.assets.pay_for_access_service(ddo, {\"from\": clef_account})\nocean.assets.download_asset(ddo, clef_account, './', order_tx_id)\n\n```\n\nPlease note that you need to consult your clef console periodically to approve transactions and input your password if needed.\nYou can use the ClefAccount object seamlessly, in any transaction, just like regular Accounts. Simply send your transaction with `{\"from\": clef_account}` where needed.\n"
  },
  {
    "path": "bumpversion.sh",
    "content": "#!/bin/bash\n##\n## Copyright 2023 Ocean Protocol Foundation\n## SPDX-License-Identifier: Apache-2.0\n##\n\nset -x\nset -e\n\nusage(){\n  echo \"Usage: $0 {major|minor|patch} [--tag]\"\n  exit 1\n}\n\nif ! [ -x \"$(command -v bumpversion)\" ]; then\n  echo 'Error: bumpversion is not installed.' >&2\n  exit 1\nelif ! git diff-index --quiet HEAD -- >/dev/null 2>&1; then\n  echo 'There are local changes in your the git repository. Please commit or stash them before bumping version.' >&2\n  exit 1\nfi\n\nif [ \"$#\" -lt 1 ]; then\n    echo \"Illegal number of parameters\"\n    usage\nelif [[ $1 != 'major' && $1 != 'minor' && $1 != 'patch' ]]; then\n    echo 'First argument must be {major|minor|patch}'\n    usage\nfi\n\nif [[ $2 == '--tag' ]]; then\n  if git branch --contains $(git rev-parse --verify HEAD) | grep -E 'main'; then\n    eval \"bumpversion --tag --commit $1\"\n  else\n    echo \"Only main tags can be tagged\"\n    exit 1\n  fi\nelse\n  eval \"bumpversion --no-tag $1\"\nfi\n\n"
  },
  {
    "path": "conftest.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n# This is the global conftest. EVERY SINGLE TEST looks at this\n# - For tests that use ganache, import conftest_ganache.py. Just don't put it here.\n# - For tests that use remote networks, do your own thing. Just don't put it here.\n"
  },
  {
    "path": "conftest_ganache.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom typing import Tuple\n\nimport pytest\n\nfrom ocean_lib.example_config import get_config_dict\nfrom ocean_lib.models.data_nft import DataNFT\nfrom ocean_lib.models.data_nft_factory import DataNFTFactoryContract\nfrom ocean_lib.models.datatoken1 import Datatoken1\nfrom ocean_lib.models.factory_router import FactoryRouter\nfrom ocean_lib.models.fixed_rate_exchange import FixedRateExchange\nfrom ocean_lib.ocean.util import get_address_of_type, send_ether, to_wei\nfrom ocean_lib.web3_internal.contract_utils import get_contracts_addresses_all_networks\nfrom tests.resources.helper_functions import (\n    deploy_erc721_erc20,\n    get_another_consumer_wallet,\n    get_consumer_ocean_instance,\n    get_consumer_wallet,\n    get_factory_deployer_wallet,\n    get_file1,\n    get_file2,\n    get_file3,\n    get_ganache_wallet,\n    get_provider_wallet,\n    get_publisher_ocean_instance,\n    get_publisher_wallet,\n    get_wallet,\n    setup_logging,\n)\n\n_NETWORK = \"ganache\"\n\nsetup_logging()\n\n\n@pytest.fixture(autouse=True)\ndef setup_all(request, config, ocean_token):\n    # a test can skip setup_all() via decorator \"@pytest.mark.nosetup_all\"\n    if \"nosetup_all\" in request.keywords:\n        return\n\n    wallet = get_ganache_wallet()\n\n    if not wallet:\n        return\n\n    if not get_contracts_addresses_all_networks(config):\n        print(\"Can not find adddresses.\")\n        return\n\n    balance = config[\"web3_instance\"].eth.get_balance(wallet.address)\n    assert balance >= to_wei(10), \"Need more ETH\"\n\n    amt_distribute = to_wei(1000)\n    ocean_token.mint(wallet, to_wei(2000), {\"from\": wallet})\n\n    for w in (get_publisher_wallet(), get_consumer_wallet()):\n        balance = config[\"web3_instance\"].eth.get_balance(w.address)\n\n        if balance < to_wei(2):\n            send_ether(config, wallet, w.address, to_wei(4))\n\n        if ocean_token.balanceOf(w) < to_wei(100):\n            ocean_token.mint(w, amt_distribute, {\"from\": wallet})\n\n\n@pytest.fixture\ndef config():\n    return get_config_dict()\n\n\n@pytest.fixture\ndef publisher_ocean():\n    return get_publisher_ocean_instance()\n\n\n@pytest.fixture\ndef basic_asset(publisher_ocean, publisher_wallet):\n    name = \"Branin dataset\"\n    url = \"https://raw.githubusercontent.com/trentmc/branin/main/branin.arff\"\n\n    (data_nft, datatoken, ddo) = publisher_ocean.assets.create_url_asset(\n        name, url, {\"from\": publisher_wallet}\n    )\n\n    assert ddo.nft[\"name\"] == name\n    assert len(ddo.datatokens) == 1\n\n    return (data_nft, datatoken, ddo)\n\n\n@pytest.fixture\ndef consumer_ocean():\n    return get_consumer_ocean_instance()\n\n\n@pytest.fixture\ndef publisher_wallet():\n    return get_publisher_wallet()\n\n\n@pytest.fixture\ndef consumer_wallet():\n    return get_consumer_wallet()\n\n\n@pytest.fixture\ndef another_consumer_wallet():\n    return get_another_consumer_wallet()\n\n\n@pytest.fixture\ndef factory_deployer_wallet(config):\n    return get_factory_deployer_wallet(config)\n\n\n@pytest.fixture\ndef ocean_address(config) -> str:\n    return get_address_of_type(config, \"Ocean\")\n\n\n@pytest.fixture\ndef ocean_token(config, ocean_address) -> Datatoken1:\n    return Datatoken1(config, ocean_address)\n\n\n@pytest.fixture\ndef factory_router(config):\n    return FactoryRouter(config, get_address_of_type(config, \"Router\"))\n\n\n@pytest.fixture\ndef data_nft_factory(config):\n    return DataNFTFactoryContract(config, get_address_of_type(config, \"ERC721Factory\"))\n\n\n@pytest.fixture\ndef provider_wallet():\n    return get_provider_wallet()\n\n\n@pytest.fixture\ndef file1():\n    return get_file1()\n\n\n@pytest.fixture\ndef file2():\n    return get_file2()\n\n\n@pytest.fixture\ndef file3():\n    return get_file3()\n\n\n@pytest.fixture\ndef FRE(config) -> FixedRateExchange:\n    return FixedRateExchange(config, get_address_of_type(config, \"FixedPrice\"))\n\n\n@pytest.fixture\ndef data_nft(config, publisher_wallet) -> DataNFT:\n    return deploy_erc721_erc20(config, publisher_wallet)\n\n\n@pytest.fixture\ndef data_NFT_and_DT(config, publisher_wallet) -> Tuple[DataNFT, Datatoken1]:\n    return deploy_erc721_erc20(config, publisher_wallet, publisher_wallet)\n\n\n@pytest.fixture\ndef DT(data_NFT_and_DT) -> Datatoken1:\n    (_, DT) = data_NFT_and_DT\n    return DT\n\n\n# aliases\n@pytest.fixture\ndef OCEAN(ocean_token) -> Datatoken1:\n    return ocean_token\n\n\n@pytest.fixture\ndef alice(publisher_wallet):\n    return publisher_wallet\n\n\n@pytest.fixture\ndef bob(consumer_wallet):\n    return consumer_wallet\n\n\n@pytest.fixture\ndef carlos():\n    return get_wallet(8)\n\n\n@pytest.fixture\ndef dan():\n    return get_wallet(7)\n"
  },
  {
    "path": "ocean_lib/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n\"\"\"Initialises ocean lib package.\"\"\"\n\n__author__ = \"\"\"OceanProtocol\"\"\"\n# fmt: off\n__version__ = '3.1.2'\n# fmt: on\n"
  },
  {
    "path": "ocean_lib/agreements/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "ocean_lib/agreements/consumable.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom enforce_typing import enforce_types\n\n\nclass ConsumableCodes:\n    \"\"\"\n    Contains constant values for:\n     - OK\n     - ASSET_DISABLED\n     - CONNECTIVITY_FAIL\n     - CREDENTIAL_NOT_IN_ALLOW_LIST\n     - CREDENTIAL_IN_DENY_LIST\n    \"\"\"\n\n    OK = 0\n    ASSET_DISABLED = 1\n    CONNECTIVITY_FAIL = 2\n    CREDENTIAL_NOT_IN_ALLOW_LIST = 3\n    CREDENTIAL_IN_DENY_LIST = 4\n    ASSET_UNLISTED = 5\n\n\nclass MalformedCredential(Exception):\n    pass\n\n\nclass AssetNotConsumable(Exception):\n    @enforce_types\n    def __init__(self, consumable_code: int) -> None:\n        self.consumable_code = consumable_code\n"
  },
  {
    "path": "ocean_lib/agreements/service_types.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\"\"\"Agreements module.\"\"\"\n\n\nclass ServiceTypes:\n    \"\"\"Types of Service allowed in ocean protocol DDO services for V4.\"\"\"\n\n    ASSET_ACCESS = \"access\"\n    CLOUD_COMPUTE = \"compute\"\n    AUTHORIZATION = \"wss\"\n\n\nclass ServiceTypesNames:\n    DEFAULT_ACCESS_NAME = \"Download service\"\n    DEFAULT_COMPUTE_NAME = \"Compute service\"\n"
  },
  {
    "path": "ocean_lib/aquarius/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\"\"\"Ocean Aquarius module.\"\"\"\nfrom .aquarius import Aquarius  # noqa\n"
  },
  {
    "path": "ocean_lib/aquarius/aquarius.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\"\"\"\nAquarius module.\nHelp to communicate with the metadata store.\n\"\"\"\n\nimport json\nimport logging\nimport time\nfrom typing import Optional, Tuple, Union\n\nfrom enforce_typing import enforce_types\n\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.http_requests.requests_session import get_requests_session\n\nlogger = logging.getLogger(\"aquarius\")\n\n\nclass Aquarius:\n    \"\"\"Aquarius wrapper to call different endpoint of aquarius component.\"\"\"\n\n    @enforce_types\n    def __init__(self, aquarius_url: str) -> None:\n        \"\"\"\n        This class wraps Aquarius REST API.\n\n        :param aquarius_url: Url of the aquarius instance.\n        \"\"\"\n        assert aquarius_url, f'Invalid url \"{aquarius_url}\"'\n        # :HACK:\n        if \"/api/aquarius/assets\" in aquarius_url:\n            aquarius_url = aquarius_url[: aquarius_url.find(\"/api/aquarius/assets\")]\n\n        self.requests_session = get_requests_session()\n        try:\n            response = self.requests_session.get(f\"{aquarius_url}\")\n        except Exception:\n            response = None\n\n        if not response or response.status_code != 200:\n            raise Exception(f\"Invalid or unresponsive aquarius url {aquarius_url}\")\n\n        self.base_url = f\"{aquarius_url}/api/aquarius/assets\"\n\n        logging.debug(f\"Aquarius connected at {aquarius_url}\")\n        logging.debug(f\"Aquarius API documentation at {aquarius_url}/api/v1/docs\")\n        logging.debug(f\"Metadata assets (DDOs) at {self.base_url}\")\n\n    @classmethod\n    def get_instance(cls, metadata_cache_uri: str) -> \"Aquarius\":\n        return cls(metadata_cache_uri)\n\n    @enforce_types\n    def get_ddo(self, did: str) -> Optional[DDO]:\n        \"\"\"Retrieve ddo for a given did.\"\"\"\n        response = self.requests_session.get(f\"{self.base_url}/ddo/{did}\")\n\n        if response.status_code == 200:\n            response_dict = response.json()\n\n            return DDO.from_dict(response_dict)\n\n        return None\n\n    @enforce_types\n    def ddo_exists(self, did: str) -> bool:\n        \"\"\"Is this DDO in Aqua?\"\"\"\n        response = self.requests_session.get(f\"{self.base_url}/ddo/{did}\").content\n        return f\"Asset DID {did} not found in Elasticsearch\" not in str(response)\n\n    @enforce_types\n    def get_ddo_metadata(self, did: str) -> dict:\n        \"\"\"Returns a given DDO's \"metadata\" field values\"\"\"\n        response = self.requests_session.get(f\"{self.base_url}/metadata/{did}\")\n        if response.status_code == 200:\n            return response.json()\n\n        return {}\n\n    @enforce_types\n    def query_search(self, search_query: dict) -> list:\n        \"\"\"\n        Search using a query.\n\n        Currently implemented is the MongoDB query model to search for documents according to:\n        https://docs.mongodb.com/manual/tutorial/query-documents/\n\n        And an Elastic Search driver, which implements a basic parser to convert the query into\n        elastic search format.\n\n        Example: query_search({\"price\":[0,10]})\n\n        :param search_query: Python dictionary, query following elasticsearch syntax\n        :return: List of DDO\n        \"\"\"\n        response = self.requests_session.post(\n            f\"{self.base_url}/query\",\n            data=json.dumps(search_query),\n            headers={\"content-type\": \"application/json\"},\n        )\n\n        if response.status_code == 200:\n            return response.json()[\"hits\"][\"hits\"]\n\n        raise ValueError(f\"Unable to search for DDO: {response.content}\")\n\n    @enforce_types\n    def validate_ddo(self, ddo: DDO) -> Tuple[bool, Union[list, dict]]:\n        \"\"\"Does the DDO conform to the Ocean DDO schema?\n        Schema definition: https://docs.oceanprotocol.com/core-concepts/did-ddo\n        \"\"\"\n        ddo_dict = ddo.as_dictionary()\n        data = json.dumps(ddo_dict, separators=(\",\", \":\")).encode(\"utf-8\")\n\n        response = self.requests_session.post(\n            f\"{self.base_url.replace('/v1/', '/')}/ddo/validate\",\n            data=data,\n            headers={\"content-type\": \"application/octet-stream\"},\n        )\n\n        parsed_response = response.json()\n\n        if parsed_response.get(\"hash\"):\n            return True, parsed_response\n\n        return False, parsed_response\n\n    @enforce_types\n    def wait_for_ddo(self, did: str, timeout=60):\n        start = time.time()\n        ddo = None\n        while not ddo:\n            ddo = self.get_ddo(did)\n\n            if not ddo:\n                time.sleep(0.2)\n\n            if time.time() - start > timeout:\n                break\n\n        return ddo\n\n    @enforce_types\n    def wait_for_ddo_update(self, ddo: DDO, tx: str):\n        start = time.time()\n        ddo2 = None\n        while True:\n            try:\n                ddo2 = self.get_ddo(ddo.did)\n            except ValueError:\n                pass\n            if not ddo2:\n                time.sleep(0.2)\n            elif ddo2.event.get(\"tx\") == tx:\n                logger.debug(\n                    f\"Transaction matching the given tx id detected in metadata store. ddo2.event = {ddo2.event}\"\n                )\n                break\n\n            elapsed_time = time.time() - start\n            if elapsed_time > 60:\n                break\n\n        return ddo2\n"
  },
  {
    "path": "ocean_lib/aquarius/test/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "ocean_lib/aquarius/test/conftest.py",
    "content": "from conftest_ganache import *\n"
  },
  {
    "path": "ocean_lib/aquarius/test/test_aquarius.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\nfrom ocean_lib.aquarius.aquarius import Aquarius\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.example_config import METADATA_CACHE_URI\n\n\n@pytest.mark.unit\ndef test_init():\n    \"\"\"Tests initialisation of Aquarius objects.\"\"\"\n    aqua = Aquarius(\"http://172.15.0.5:5000/api/aquarius/assets\")\n    assert aqua.base_url == \"http://172.15.0.5:5000/api/aquarius/assets\"\n\n\n@pytest.mark.integration\ndef test_aqua_functions_for_single_ddo(publisher_ocean, publisher_wallet, basic_asset):\n    \"\"\"Tests against single-ddo functions of Aquarius.\"\"\"\n    aquarius = publisher_ocean.assets._aquarius\n\n    _, _, ddo1 = basic_asset\n    metadata1 = ddo1.metadata\n\n    ddo2 = aquarius.wait_for_ddo(ddo1.did)\n    assert ddo2.metadata == ddo1.metadata\n\n    ddo3 = publisher_ocean.assets.resolve(ddo1.did)\n    assert ddo3.did == ddo1.did, \"Aquarius could not resolve the did.\"\n    assert ddo3.did == ddo2.did, \"Aquarius could not resolve the did.\"\n\n    aqua_uri = publisher_ocean.config_dict.get(\"METADATA_CACHE_URI\")\n    ddo4 = Aquarius.get_instance(aqua_uri).get_ddo(ddo2.did)\n    assert isinstance(ddo4, DDO)\n    assert ddo4.did == ddo2.did, \"Aquarius could not resolve the did.\"\n\n    metadata2 = aquarius.get_ddo_metadata(ddo2.did)\n    assert metadata2 == metadata1\n\n\n@pytest.mark.unit\ndef test_invalid_search_query():\n    \"\"\"Tests query search with an invalid query.\"\"\"\n    aquarius = Aquarius.get_instance(METADATA_CACHE_URI)\n    search_query = \"not_a_dict\"\n    with pytest.raises(TypeError):\n        aquarius.query_search(search_query=search_query)\n\n\n@pytest.mark.unit\ndef test_empty_responses():\n    aquarius = Aquarius.get_instance(METADATA_CACHE_URI)\n    assert aquarius.get_ddo_metadata(\"inexistent_ddo\") == {}\n"
  },
  {
    "path": "ocean_lib/assets/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "ocean_lib/assets/asset_downloader.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\nimport logging\nimport os\nfrom typing import Optional, Union\n\nfrom enforce_typing import enforce_types\n\nfrom ocean_lib.agreements.consumable import AssetNotConsumable, ConsumableCodes\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider\nfrom ocean_lib.services.service import Service\n\nlogger = logging.getLogger(__name__)\n\n\n@enforce_types\ndef download_asset_files(\n    ddo: DDO,\n    service: Service,\n    consumer_wallet,\n    destination: str,\n    order_tx_id: Union[str, bytes],\n    index: Optional[int] = None,\n    userdata: Optional[dict] = None,\n) -> str:\n    \"\"\"Download asset data file or result file from compute job.\n\n    :param ddo: DDO instance\n    :param service: Sevice instance\n    :param consumer_wallet: Wallet instance of the consumer\n    :param destination: Path, str\n    :param order_tx_id: hex str or hex bytes the transaction hash of the startOrder tx\n    :param index: Index of the document that is going to be downloaded, Optional[int]\n    :param userdata: Dict of additional data from user\n    :return: asset folder path, str\n    \"\"\"\n    data_provider = DataServiceProvider\n\n    if not service.service_endpoint:\n        logger.error(\n            'Consume asset failed, service definition is missing the \"serviceEndpoint\".'\n        )\n        raise AssertionError(\n            'Consume asset failed, service definition is missing the \"serviceEndpoint\".'\n        )\n\n    if index is not None:\n        assert isinstance(index, int), logger.error(\"index has to be an integer.\")\n        assert index >= 0, logger.error(\"index has to be 0 or a positive integer.\")\n\n    consumable_result = is_consumable(\n        ddo,\n        service,\n        {\"type\": \"address\", \"value\": consumer_wallet.address},\n        with_connectivity_check=True,\n        userdata=userdata,\n    )\n    if consumable_result != ConsumableCodes.OK:\n        raise AssetNotConsumable(consumable_result)\n\n    service_index_in_asset = ddo.get_index_of_service(service)\n    asset_folder = os.path.join(\n        destination, f\"datafile.{ddo.did},{service_index_in_asset}\"\n    )\n\n    if not os.path.exists(asset_folder):\n        os.makedirs(asset_folder)\n\n    data_provider.download(\n        did=ddo.did,\n        service=service,\n        tx_id=order_tx_id,\n        consumer_wallet=consumer_wallet,\n        destination_folder=asset_folder,\n        index=index,\n        userdata=userdata,\n    )\n\n    return asset_folder\n\n\n@enforce_types\ndef is_consumable(\n    ddo: DDO,\n    service: Service,\n    credential: Optional[dict] = None,\n    with_connectivity_check: bool = True,\n    userdata: Optional[dict] = None,\n) -> bool:\n    \"\"\"Checks whether an asset is consumable and returns a ConsumableCode.\"\"\"\n    if ddo.is_disabled:\n        return ConsumableCodes.ASSET_DISABLED\n\n    if with_connectivity_check and not DataServiceProvider.check_asset_file_info(\n        ddo.did, service.id, service.service_endpoint, userdata=userdata\n    ):\n        return ConsumableCodes.CONNECTIVITY_FAIL\n\n    # to be parameterized in the future, can implement other credential classes\n    if ddo.requires_address_credential:\n        return ddo.validate_access(credential)\n\n    return ConsumableCodes.OK\n"
  },
  {
    "path": "ocean_lib/assets/credentials.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom typing import Optional\n\nfrom enforce_typing import enforce_types\n\nfrom ocean_lib.agreements.consumable import ConsumableCodes, MalformedCredential\n\n\nclass AddressCredentialMixin:\n    @enforce_types\n    def get_addresses_of_class(self, access_class: str = \"allow\") -> list:\n        \"\"\"Get a filtered list of addresses from credentials (use with allow/deny).\"\"\"\n        address_entry = self.get_address_entry_of_class(access_class)\n        if not address_entry:\n            return []\n\n        if \"values\" not in address_entry:\n            raise MalformedCredential(\"No values key in the address credential.\")\n\n        return [addr.lower() for addr in address_entry[\"values\"]]\n\n    @enforce_types\n    def requires_credential(self) -> bool:\n        \"\"\"Checks whether the ddo requires an address credential.\"\"\"\n        allowed_addresses = self.get_addresses_of_class(\"allow\")\n        denied_addresses = self.get_addresses_of_class(\"deny\")\n\n        return bool(allowed_addresses or denied_addresses)\n\n    @enforce_types\n    def validate_access(self, credential: Optional[dict] = None) -> int:\n        \"\"\"Checks a credential dictionary against the address allow/deny lists.\"\"\"\n        address = simplify_credential_to_address(credential)\n\n        allowed_addresses = self.get_addresses_of_class(\"allow\")\n        denied_addresses = self.get_addresses_of_class(\"deny\")\n\n        if not address and not self.requires_credential():\n            return ConsumableCodes.OK\n\n        if allowed_addresses and address.lower() not in allowed_addresses:\n            return ConsumableCodes.CREDENTIAL_NOT_IN_ALLOW_LIST\n\n        if not allowed_addresses and address.lower() in denied_addresses:\n            return ConsumableCodes.CREDENTIAL_IN_DENY_LIST\n\n        return ConsumableCodes.OK\n\n    @enforce_types\n    def add_address_to_access_class(\n        self, address: str, access_class: str = \"allow\"\n    ) -> None:\n        \"\"\"Adds an address to an address list (either allow or deny).\"\"\"\n        address = address.lower()\n\n        if not self.credentials or access_class not in self.credentials:\n            self.credentials[access_class] = [{\"type\": \"address\", \"values\": [address]}]\n            return\n\n        address_entry = self.get_address_entry_of_class(access_class)\n\n        if not address_entry:\n            self.credentials[access_class].append(\n                {\"type\": \"address\", \"values\": [address]}\n            )\n            return\n\n        lc_addresses = self.get_addresses_of_class(access_class)\n\n        if address not in lc_addresses:\n            lc_addresses.append(address)\n\n        address_entry[\"values\"] = lc_addresses\n\n    @enforce_types\n    def remove_address_from_access_class(\n        self, address: str, access_class: str = \"allow\"\n    ) -> None:\n        \"\"\"Removes an address from an address list (either allow or deny)i.\"\"\"\n        address = address.lower()\n\n        if not self.credentials or access_class not in self.credentials:\n            return\n\n        address_entry = self.get_address_entry_of_class(access_class)\n\n        if not address_entry:\n            return\n\n        lc_addresses = self.get_addresses_of_class(access_class)\n\n        if address not in lc_addresses:\n            return\n\n        lc_addresses.remove(address)\n        address_entry[\"values\"] = lc_addresses\n\n    @enforce_types\n    def get_address_entry_of_class(self, access_class: str = \"allow\") -> Optional[dict]:\n        \"\"\"Get address credentials entry of the specified access class. access_class = \"allow\" or \"deny\".\"\"\"\n        entries = self.credentials.get(access_class, [])\n        address_entries = [entry for entry in entries if entry.get(\"type\") == \"address\"]\n        return address_entries[0] if address_entries else None\n\n\n@enforce_types\ndef simplify_credential_to_address(credential: Optional[dict]) -> Optional[str]:\n    \"\"\"Extracts address value from credential dictionary.\"\"\"\n    if not credential:\n        return None\n\n    if not credential.get(\"value\"):\n        raise MalformedCredential(\"Received empty address.\")\n\n    return credential[\"value\"]\n"
  },
  {
    "path": "ocean_lib/assets/ddo.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport copy\nimport logging\nfrom typing import Optional\n\nfrom enforce_typing import enforce_types\n\nfrom ocean_lib.assets.credentials import AddressCredentialMixin\nfrom ocean_lib.data_provider.fileinfo_provider import FileInfoProvider\nfrom ocean_lib.ocean.util import create_checksum\nfrom ocean_lib.services.service import Service\n\nlogger = logging.getLogger(\"ddo\")\n\n\nclass DDO(AddressCredentialMixin):\n    \"\"\"Create, import, export, validate DDO objects.\"\"\"\n\n    @enforce_types\n    def __init__(\n        self,\n        did: Optional[str] = None,\n        context: Optional[list] = None,\n        chain_id: Optional[int] = None,\n        nft_address: Optional[str] = None,\n        metadata: Optional[dict] = None,\n        services: Optional[list] = None,\n        credentials: Optional[dict] = None,\n        nft: Optional[dict] = None,\n        datatokens: Optional[list] = None,\n        event: Optional[dict] = None,\n        stats: Optional[dict] = None,\n    ) -> None:\n        self.did = did\n        self.context = context or [\"https://w3id.org/did/v1\"]\n        self.chain_id = chain_id\n        self.nft_address = nft_address\n        self.metadata = metadata\n        self.version = \"4.1.0\"\n        self.services = services or []\n        self.credentials = credentials or {}\n        self.nft = nft\n        self.datatokens = datatokens\n        self.event = event\n        self.stats = stats\n\n    @property\n    @enforce_types\n    def requires_address_credential(self) -> bool:\n        \"\"\"Checks if an address credential is required on this ddo.\"\"\"\n        return self.requires_credential()\n\n    @property\n    @enforce_types\n    def allowed_addresses(self) -> list:\n        \"\"\"Lists addresses that are explicitly allowed in credentials.\"\"\"\n        return self.get_addresses_of_class(\"allow\")\n\n    @property\n    @enforce_types\n    def denied_addresses(self) -> list:\n        \"\"\"Lists addresses that are explicitly denied in credentials.\"\"\"\n        return self.get_addresses_of_class(\"deny\")\n\n    @enforce_types\n    def add_address_to_allow_list(self, address: str) -> None:\n        \"\"\"Adds an address to allowed addresses list.\"\"\"\n        self.add_address_to_access_class(address, \"allow\")\n\n    @enforce_types\n    def add_address_to_deny_list(self, address: str) -> None:\n        \"\"\"Adds an address to the denied addresses list.\"\"\"\n        self.add_address_to_access_class(address, \"deny\")\n\n    @enforce_types\n    def remove_address_from_allow_list(self, address: str) -> None:\n        \"\"\"Removes address from allow list (if it exists).\"\"\"\n        self.remove_address_from_access_class(address, \"allow\")\n\n    @enforce_types\n    def remove_address_from_deny_list(self, address: str) -> None:\n        \"\"\"Removes address from deny list (if it exists).\"\"\"\n        self.remove_address_from_access_class(address, \"deny\")\n\n    @classmethod\n    @enforce_types\n    def from_dict(cls, dictionary: dict) -> \"DDO\":\n        \"\"\"Import a JSON dict into this DDO.\"\"\"\n        values = copy.deepcopy(dictionary)\n\n        services = (\n            []\n            if \"services\" not in values\n            else [Service.from_dict(value) for value in values.pop(\"services\")]\n        )\n\n        args = [\n            values.pop(\"id\", None),\n            values.pop(\"@context\", None),\n            values.pop(\"chainId\", None),\n            values.pop(\"nftAddress\", None),\n            values.pop(\"metadata\", None),\n            services,\n            values.pop(\"credentials\", None),\n            values.pop(\"nft\", None),\n            values.pop(\"datatokens\", None),\n            values.pop(\"event\", None),\n            values.pop(\"stats\", None),\n        ]\n\n        if args[0] is None:\n            return UnavailableDDO(*args)\n\n        return cls(*args)\n\n    @enforce_types\n    def as_dictionary(self) -> dict:\n        \"\"\"\n        Return the DDO as a JSON dict.\n\n        :return: dict\n        \"\"\"\n\n        data = {\n            \"@context\": self.context,\n            \"id\": self.did,\n            \"version\": self.version,\n            \"chainId\": self.chain_id,\n        }\n\n        data[\"nftAddress\"] = self.nft_address\n\n        services = [value.as_dictionary() for value in self.services]\n        args = [\"metadata\", \"credentials\", \"nft\", \"datatokens\", \"event\", \"stats\"]\n        attrs = list(\n            filter(\n                lambda attr: not not attr[1],\n                map(lambda attr: (attr, getattr(self, attr, None)), args),\n            )\n        )\n        attrs.append((\"services\", services))\n        data.update(attrs)\n        return data\n\n    @enforce_types\n    def add_service(self, service: Service) -> None:\n        \"\"\"\n        Add a service to the list of services on the V4 DDO.\n\n        :param service: To add service, Service\n        \"\"\"\n        service.encrypt_files(self.nft_address, self.chain_id)\n\n        logger.debug(\n            f\"Adding service with service type {service.type} with did {self.did}\"\n        )\n        self.services.append(service)\n\n    @enforce_types\n    def create_compute_service(\n        self,\n        service_id: str,\n        service_endpoint: str,\n        datatoken_address: str,\n        files,\n        compute_values: Optional[dict] = None,\n        timeout: Optional[int] = 3600,\n    ) -> None:\n        if not compute_values:\n            compute_values = {\n                \"allowRawAlgorithm\": False,\n                \"allowNetworkAccess\": True,\n                \"publisherTrustedAlgorithms\": [],\n                \"publisherTrustedAlgorithmPublishers\": [],\n            }\n\n        compute_service = Service(\n            service_id=service_id,\n            service_type=\"compute\",\n            service_endpoint=service_endpoint,\n            datatoken=datatoken_address,\n            files=files,\n            timeout=timeout,\n            compute_values=compute_values,\n        )\n\n        self.add_service(compute_service)\n\n    @enforce_types\n    def get_service_by_id(self, service_id: str) -> Service:\n        \"\"\"Return Service with the given id.\n        Return None if service with the given id not found.\"\"\"\n        return next(\n            (service for service in self.services if service.id == service_id), None\n        )\n\n    @enforce_types\n    def get_service_by_index(self, service_index: int) -> Service:\n        \"\"\"Return Service with the given index.\n        Return None if service with the given index not found.\"\"\"\n        return (\n            self.services[service_index] if service_index < len(self.services) else None\n        )\n\n    @enforce_types\n    def get_index_of_service(self, service: Service) -> int:\n        \"\"\"Return index of the given Service.\n        Return None if service was not found.\"\"\"\n        return next(\n            (\n                index\n                for index, this_service in enumerate(self.services)\n                if this_service.id == service.id\n            ),\n            None,\n        )\n\n    @enforce_types\n    def generate_trusted_algorithms(self) -> dict:\n        \"\"\"Returns a trustedAlgorithm dictionary for service at index 0.\"\"\"\n        resp = FileInfoProvider.fileinfo(\n            self.did, self.get_service_by_index(0), with_checksum=True\n        )\n        files_checksum = [resp_item[\"checksum\"] for resp_item in resp.json()]\n        container = self.metadata[\"algorithm\"][\"container\"]\n        return {\n            \"did\": self.did,\n            \"filesChecksum\": \"\".join(files_checksum),\n            \"containerSectionChecksum\": create_checksum(\n                container[\"entrypoint\"] + container[\"checksum\"]\n            ),\n        }\n\n    @property\n    def is_disabled(self) -> bool:\n        return not self.metadata or (self.nft and self.nft[\"state\"] not in [0, 5])\n\n\nclass UnavailableDDO(DDO):\n    pass\n"
  },
  {
    "path": "ocean_lib/assets/test/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "ocean_lib/assets/test/conftest.py",
    "content": "from conftest_ganache import *\n"
  },
  {
    "path": "ocean_lib/assets/test/test_asset_downloader.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport os\nfrom unittest.mock import patch\n\nimport pytest\nfrom requests.exceptions import InvalidURL\n\nfrom ocean_lib.agreements.consumable import AssetNotConsumable, ConsumableCodes\nfrom ocean_lib.agreements.service_types import ServiceTypes\nfrom ocean_lib.assets.asset_downloader import download_asset_files, is_consumable\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider\nfrom ocean_lib.models.datatoken_base import TokenFeeInfo\nfrom ocean_lib.ocean.util import to_wei\nfrom ocean_lib.services.service import Service\nfrom tests.resources.ddo_helpers import get_first_service_by_type, get_sample_ddo\n\n\n@pytest.mark.unit\ndef test_is_consumable():\n    ddo_dict = get_sample_ddo()\n    ddo = DDO.from_dict(ddo_dict)\n    service_dict = ddo_dict[\"services\"][0]\n    service = Service.from_dict(service_dict)\n    with patch(\n        \"ocean_lib.assets.test.test_asset_downloader.DataServiceProvider.check_asset_file_info\",\n        return_value=False,\n    ):\n        assert (\n            is_consumable(ddo, service, {}, True) == ConsumableCodes.CONNECTIVITY_FAIL\n        )\n\n    with patch(\n        \"ocean_lib.assets.test.test_asset_downloader.DataServiceProvider.check_asset_file_info\",\n        return_value=True,\n    ):\n        assert (\n            is_consumable(ddo, service, {\"type\": \"address\", \"value\": \"0xdddd\"}, True)\n            == ConsumableCodes.CREDENTIAL_NOT_IN_ALLOW_LIST\n        )\n\n\n@pytest.mark.unit\ndef test_ocean_assets_download_failure(publisher_wallet):\n    \"\"\"Tests that downloading from an empty service raises an AssertionError.\"\"\"\n\n    ddo_dict = get_sample_ddo()\n    ddo = DDO.from_dict(ddo_dict)\n    access_service = get_first_service_by_type(ddo, ServiceTypes.ASSET_ACCESS)\n    access_service.service_endpoint = None\n    ddo.services[0] = access_service\n\n    with pytest.raises(AssertionError):\n        download_asset_files(\n            ddo,\n            access_service,\n            publisher_wallet,\n            \"test_destination\",\n            \"test_order_tx_id\",\n        )\n\n\n@pytest.mark.unit\ndef test_invalid_provider_uri(publisher_wallet):\n    \"\"\"Tests with invalid provider URI that raise AssertionError.\"\"\"\n    ddo_dict = get_sample_ddo()\n    ddo = DDO.from_dict(ddo_dict)\n    ddo.services[0].service_endpoint = \"http://nothing-here.com\"\n\n    with pytest.raises(InvalidURL):\n        download_asset_files(\n            ddo,\n            ddo.services[0],\n            publisher_wallet,\n            \"test_destination\",\n            \"test_order_tx_id\",\n        )\n\n\n@pytest.mark.unit\ndef test_invalid_state(publisher_wallet):\n    \"\"\"Tests different scenarios that raise AssetNotConsumable.\"\"\"\n    ddo_dict = get_sample_ddo()\n    ddo = DDO.from_dict(ddo_dict)\n    ddo.nft[\"state\"] = 1\n\n    with pytest.raises(AssetNotConsumable):\n        download_asset_files(\n            ddo,\n            ddo.services[0],\n            publisher_wallet,\n            \"test_destination\",\n            \"test_order_tx_id\",\n        )\n\n    ddo.metadata = []\n    with pytest.raises(AssetNotConsumable):\n        download_asset_files(\n            ddo,\n            ddo.services[0],\n            publisher_wallet,\n            \"test_destination\",\n            \"test_order_tx_id\",\n        )\n\n\n@pytest.mark.integration\ndef test_ocean_assets_download_indexes(publisher_wallet):\n    \"\"\"Tests different values of indexes that raise AssertionError.\"\"\"\n\n    ddo_dict = get_sample_ddo()\n    ddo = DDO.from_dict(ddo_dict)\n\n    index = range(3)\n    with pytest.raises(TypeError):\n        download_asset_files(\n            ddo,\n            ddo.services[0],\n            publisher_wallet,\n            \"test_destination\",\n            \"test_order_tx_id\",\n            index=index,\n        )\n\n    index = -1\n    with pytest.raises(AssertionError):\n        download_asset_files(\n            ddo,\n            ddo.services[0],\n            publisher_wallet,\n            \"test_destination\",\n            \"test_order_tx_id\",\n            index=index,\n        )\n\n\n@pytest.mark.integration\ndef test_ocean_assets_download_destination_file(\n    tmpdir,\n    publisher_ocean,\n    publisher_wallet,\n    basic_asset,\n):\n    \"\"\"Convert tmpdir: py._path.local.LocalPath to str, satisfy enforce-typing.\"\"\"\n    data_provider = DataServiceProvider\n    data_nft, datatoken, ddo = basic_asset\n    access_service = get_first_service_by_type(ddo, ServiceTypes.ASSET_ACCESS)\n\n    datatoken.mint(\n        publisher_wallet.address,\n        to_wei(50),\n        {\"from\": publisher_wallet},\n    )\n\n    initialize_response = data_provider.initialize(\n        did=ddo.did,\n        service=access_service,\n        consumer_address=publisher_wallet.address,\n    )\n\n    provider_fees = initialize_response.json()[\"providerFee\"]\n    consume_market_fees = TokenFeeInfo(\n        address=publisher_wallet.address,\n        token=datatoken.address,\n    )\n\n    receipt = datatoken.start_order(\n        consumer=publisher_wallet.address,\n        service_index=ddo.get_index_of_service(access_service),\n        provider_fees=provider_fees,\n        consume_market_fees=consume_market_fees,\n        tx_dict={\"from\": publisher_wallet},\n    )\n\n    orders = publisher_ocean.get_user_orders(\n        publisher_wallet.address, datatoken.address\n    )\n    assert datatoken.address in [order.address for order in orders]\n    assert receipt.transactionHash.hex() in [\n        order.transactionHash.hex() for order in orders\n    ]\n\n    written_path = download_asset_files(\n        ddo,\n        access_service,\n        publisher_wallet,\n        str(tmpdir),\n        receipt.transactionHash.hex(),\n    )\n\n    assert os.path.exists(written_path)\n\n    # index not found, even though tx_id exists\n    with pytest.raises(AssertionError):\n        download_asset_files(\n            ddo,\n            ddo.services[0],\n            publisher_wallet,\n            str(tmpdir),\n            receipt.transactionHash.hex(),\n            index=4,\n        )\n"
  },
  {
    "path": "ocean_lib/assets/test/test_ddo.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\nfrom ocean_lib.agreements.consumable import MalformedCredential\nfrom ocean_lib.assets.credentials import simplify_credential_to_address\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.services.service import Service\nfrom tests.resources.ddo_helpers import (\n    get_key_from_v4_sample_ddo,\n    get_sample_ddo,\n    get_sample_ddo_with_compute_service,\n)\n\n\n@pytest.mark.unit\ndef test_ddo_utils():\n    \"\"\"Tests the structure of a JSON format of the V4 DDO.\"\"\"\n    ddo_dict = get_sample_ddo()\n    assert isinstance(ddo_dict, dict)\n\n    assert isinstance(ddo_dict, dict)\n    assert ddo_dict[\"@context\"] == [\"https://w3id.org/did/v1\"]\n    context = ddo_dict[\"@context\"]\n    assert (\n        ddo_dict[\"id\"]\n        == \"did:op:d32696f71f3318c92bcf325e2e51e6e8299c0eb6d362ddcfa77d2a3e0c1237b5\"\n    )\n    did = ddo_dict[\"id\"]\n    assert ddo_dict[\"version\"] == \"4.1.0\"\n    assert ddo_dict[\"chainId\"] == 8996\n    chain_id = ddo_dict[\"chainId\"]\n\n    assert ddo_dict[\"metadata\"] == {\n        \"created\": \"2020-11-15T12:27:48Z\",\n        \"updated\": \"2021-05-17T21:58:02Z\",\n        \"description\": \"Sample description\",\n        \"name\": \"Sample asset\",\n        \"type\": \"dataset\",\n        \"author\": \"OPF\",\n        \"license\": \"https://market.oceanprotocol.com/terms\",\n    }\n    metadata = ddo_dict[\"metadata\"]\n    assert isinstance(ddo_dict[\"services\"], list)\n    assert ddo_dict[\"services\"] == [\n        {\n            \"id\": \"1\",\n            \"type\": \"access\",\n            \"files\": \"0x0000\",\n            \"name\": \"Download service\",\n            \"description\": \"Download service\",\n            \"datatokenAddress\": \"0x123\",\n            \"serviceEndpoint\": \"http://172.15.0.4:8030\",\n            \"timeout\": 0,\n        }\n    ]\n\n    services = [\n        Service.from_dict(value)\n        for value in ddo_dict[\"services\"]\n        if isinstance(value, dict)\n    ]\n\n    assert ddo_dict[\"credentials\"] == {\n        \"allow\": [{\"type\": \"address\", \"values\": [\"0x123\", \"0x456\"]}],\n        \"deny\": [{\"type\": \"address\", \"values\": [\"0x2222\", \"0x333\"]}],\n    }\n    credentials = ddo_dict[\"credentials\"]\n\n    assert ddo_dict[\"nft\"] == {\n        \"address\": \"0xCc708430E6a174BD4639A979F578A2176A0FA3fA\",\n        \"name\": \"Ocean Protocol Asset v4\",\n        \"symbol\": \"OCEAN-A-v4\",\n        \"owner\": \"0x0000000\",\n        \"state\": 0,\n        \"created\": \"2000-10-31T01:30:00\",\n    }\n    nft = ddo_dict[\"nft\"]\n\n    assert ddo_dict[\"datatokens\"] == [\n        {\n            \"address\": \"0x000000\",\n            \"name\": \"Datatoken 1\",\n            \"symbol\": \"DT-1\",\n            \"serviceId\": \"1\",\n        }\n    ]\n    datatokens = ddo_dict[\"datatokens\"]\n\n    assert ddo_dict[\"event\"] == {\n        \"tx\": \"0x8d127de58509be5dfac600792ad24cc9164921571d168bff2f123c7f1cb4b11c\",\n        \"block\": 12831214,\n        \"from\": \"0xAcca11dbeD4F863Bb3bC2336D3CE5BAC52aa1f83\",\n        \"contract\": \"0x1a4b70d8c9DcA47cD6D0Fb3c52BB8634CA1C0Fdf\",\n        \"datetime\": \"2000-10-31T01:30:00\",\n    }\n    event = ddo_dict[\"event\"]\n\n    # Sample ddo\n    assert ddo_dict[\"stats\"] == {\"consumes\": 4}\n    stats = ddo_dict[\"stats\"]\n\n    ddo = DDO(\n        did=did,\n        context=context,\n        chain_id=chain_id,\n        metadata=metadata,\n        services=services,\n        credentials=credentials,\n        nft=nft,\n        nft_address=\"0xCc708430E6a174BD4639A979F578A2176A0FA3fA\",\n        datatokens=datatokens,\n        event=event,\n        stats=stats,\n    )\n    ddo_dict_v2 = ddo.as_dictionary()\n\n    ddo_v2 = DDO.from_dict(ddo_dict_v2)\n    assert ddo_v2.as_dictionary() == ddo_dict\n\n\n@pytest.mark.unit\ndef test_add_service():\n    \"\"\"Tests adding a compute service.\"\"\"\n\n    ddo_dict = get_sample_ddo()\n    ddo = DDO.from_dict(ddo_dict)\n\n    compute_values = {\n        \"namespace\": \"ocean-compute\",\n        \"cpus\": 2,\n        \"gpus\": 4,\n        \"gpuType\": \"NVIDIA Tesla V100 GPU\",\n        \"memory\": \"128M\",\n        \"volumeSize\": \"2G\",\n        \"allowRawAlgorithm\": False,\n        \"allowNetworkAccess\": True,\n        \"publisherTrustedAlgorithmPublishers\": [\"0x234\", \"0x235\"],\n        \"publisherTrustedAlgorithms\": [\n            {\n                \"did\": \"did:op:123\",\n                \"filesChecksum\": \"100\",\n                \"containerSectionChecksum\": \"200\",\n            },\n            {\n                \"did\": \"did:op:124\",\n                \"filesChecksum\": \"110\",\n                \"containerSectionChecksum\": \"210\",\n            },\n        ],\n    }\n    ddo.create_compute_service(\n        service_id=\"2\",\n        service_endpoint=\"http://172.15.0.4:8030\",\n        datatoken_address=\"0x124\",\n        files=\"0x0001\",\n        compute_values=compute_values,\n    )\n    assert len(ddo.as_dictionary()[\"services\"]) > 1\n\n    expected_access_service = get_key_from_v4_sample_ddo(\n        key=\"services\", file_name=\"ddo_v4_with_compute_service.json\"\n    )[0]\n    assert ddo.as_dictionary()[\"services\"][0] == expected_access_service\n\n    expected_compute_service = get_key_from_v4_sample_ddo(\n        key=\"services\", file_name=\"ddo_v4_with_compute_service.json\"\n    )[1]\n\n    assert ddo.as_dictionary()[\"services\"][1][\"id\"] == expected_compute_service[\"id\"]\n\n    assert (\n        ddo.as_dictionary()[\"services\"][1][\"name\"] == expected_compute_service[\"name\"]\n    )\n    assert (\n        ddo.as_dictionary()[\"services\"][1][\"description\"]\n        == expected_compute_service[\"description\"]\n    )\n    assert (\n        ddo.as_dictionary()[\"services\"][1][\"serviceEndpoint\"]\n        == expected_compute_service[\"serviceEndpoint\"]\n    )\n    assert (\n        ddo.as_dictionary()[\"services\"][1][\"datatokenAddress\"]\n        == expected_compute_service[\"datatokenAddress\"]\n    )\n    assert (\n        ddo.as_dictionary()[\"services\"][1][\"files\"] == expected_compute_service[\"files\"]\n    )\n    assert (\n        ddo.as_dictionary()[\"services\"][1][\"timeout\"]\n        == expected_compute_service[\"timeout\"]\n    )\n    assert (\n        ddo.as_dictionary()[\"services\"][1][\"compute\"]\n        == expected_compute_service[\"compute\"]\n    )\n\n\n@pytest.mark.unit\ndef test_get_service_by_id():\n    \"\"\"Tests retrieving services from the V4 DDO.\"\"\"\n    ddo_dict = get_sample_ddo_with_compute_service()\n    ddo = DDO.from_dict(ddo_dict)\n    expected_access_service = get_key_from_v4_sample_ddo(\n        key=\"services\", file_name=\"ddo_v4_with_compute_service.json\"\n    )[0]\n\n    assert ddo.get_service_by_id(\"1\").as_dictionary() == expected_access_service\n\n    expected_compute_service = get_key_from_v4_sample_ddo(\n        key=\"services\", file_name=\"ddo_v4_with_compute_service.json\"\n    )[1]\n    assert ddo.get_service_by_id(\"2\").as_dictionary() == expected_compute_service\n\n\n@pytest.mark.unit\ndef test_credentials():\n    ddo_dict = get_sample_ddo_with_compute_service()\n    ddo = DDO.from_dict(ddo_dict)\n    assert ddo.requires_address_credential\n    assert ddo.allowed_addresses == [\"0x123\", \"0x456\"]\n    assert ddo.denied_addresses == [\"0x2222\", \"0x333\"]\n\n    ddo.add_address_to_allow_list(\"0xaAA\")\n    assert \"0xaaa\" in ddo.allowed_addresses\n    ddo.remove_address_from_allow_list(\"0xaAA\")\n    assert \"0xaaa\" not in ddo.allowed_addresses\n    ddo.remove_address_from_allow_list(\"0xaAA\")\n\n    ddo.add_address_to_deny_list(\"0xaAA\")\n    assert \"0xaaa\" in ddo.denied_addresses\n    ddo.remove_address_from_deny_list(\"0xaAA\")\n    assert \"0xaaa\" not in ddo.denied_addresses\n\n    assert ddo.validate_access({\"type\": \"address\", \"value\": \"0x123\"}) == 0\n    # not allowed\n    assert ddo.validate_access({\"type\": \"address\", \"value\": \"0x444\"}) == 3\n\n    ddo_dict = get_sample_ddo_with_compute_service()\n    del ddo_dict[\"credentials\"][\"allow\"]\n    ddo = DDO.from_dict(ddo_dict)\n    assert ddo.validate_access({\"type\": \"address\", \"value\": \"0x444\"}) == 0\n    # specifically denied\n    assert ddo.validate_access({\"type\": \"address\", \"value\": \"0x333\"}) == 4\n\n    ddo_dict = get_sample_ddo_with_compute_service()\n    del ddo_dict[\"credentials\"][\"allow\"][0][\"values\"]\n    ddo = DDO.from_dict(ddo_dict)\n    with pytest.raises(\n        MalformedCredential, match=\"No values key in the address credential\"\n    ):\n        ddo.get_addresses_of_class(\"allow\")\n\n    ddo_dict = get_sample_ddo_with_compute_service()\n    del ddo_dict[\"credentials\"]\n    ddo = DDO.from_dict(ddo_dict)\n    ddo.remove_address_from_allow_list(\"0xAA\")\n    ddo.add_address_to_allow_list(\"0xAA\")\n\n    ddo_dict = get_sample_ddo_with_compute_service()\n    ddo_dict[\"credentials\"][\"allow\"] = []\n    ddo = DDO.from_dict(ddo_dict)\n    ddo.remove_address_from_allow_list(\"0xAA\")\n    ddo.add_address_to_allow_list(\"0xAA\")\n\n\n@pytest.mark.unit\ndef test_credential_simplification():\n    assert simplify_credential_to_address(None) is None\n    with pytest.raises(MalformedCredential, match=\"Received empty address.\"):\n        simplify_credential_to_address({\"malformed\": \"no value\"})\n    assert (\n        simplify_credential_to_address({\"type\": \"address\", \"value\": \"0x11\"}) == \"0x11\"\n    )\n\n\n@pytest.mark.unit\ndef test_is_disabled():\n    ddo_dict = get_sample_ddo()\n\n    for state in range(6):\n        ddo_dict[\"nft\"][\"state\"] = state\n        ddo = DDO.from_dict(ddo_dict)\n\n        # adhere to https://docs.oceanprotocol.com/core-concepts/did-ddo#state\n        if state in [0, 5]:\n            assert not ddo.is_disabled\n        else:\n            assert ddo.is_disabled\n"
  },
  {
    "path": "ocean_lib/data_provider/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "ocean_lib/data_provider/base.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n\"\"\"Provider module.\"\"\"\nimport logging\nimport os\nimport re\nfrom json import JSONDecodeError\nfrom math import ceil\nfrom typing import Dict, List, Optional, Tuple, Union\nfrom unittest.mock import Mock\n\nimport requests\nfrom enforce_typing import enforce_types\nfrom requests.exceptions import InvalidURL\nfrom requests.models import PreparedRequest, Response\nfrom requests.sessions import Session\n\nfrom ocean_lib.exceptions import DataProviderException\nfrom ocean_lib.http_requests.requests_session import get_requests_session\nfrom ocean_lib.web3_internal.clef import ClefAccount\nfrom ocean_lib.web3_internal.utils import sign_with_clef, sign_with_key\n\nlogger = logging.getLogger(__name__)\n\n\nclass DataServiceProviderBase:\n    \"\"\"DataServiceProviderBase class.\"\"\"\n\n    _http_client = get_requests_session()\n    provider_info = None\n\n    @staticmethod\n    @enforce_types\n    def get_http_client() -> Session:\n        \"\"\"Get the http client.\"\"\"\n        return DataServiceProviderBase._http_client\n\n    @staticmethod\n    @enforce_types\n    def set_http_client(http_client: Session) -> None:\n        \"\"\"Set the http client to something other than the default `requests`.\"\"\"\n        DataServiceProviderBase._http_client = http_client\n\n    @staticmethod\n    @enforce_types\n    def sign_message(wallet, msg: str, provider_uri: str) -> Tuple[str, str]:\n        method, nonce_endpoint = DataServiceProviderBase.build_endpoint(\n            \"nonce\", provider_uri\n        )\n\n        nonce_response = DataServiceProviderBase._http_method(\n            method, url=nonce_endpoint, params={\"userAddress\": wallet.address}\n        )\n\n        if (\n            not nonce_response\n            or not hasattr(nonce_response, \"status_code\")\n            or nonce_response.status_code != 200\n            or \"nonce\" not in nonce_response.json()\n        ):\n            current_nonce = 0\n        else:\n            current_nonce = int(ceil(float(nonce_response.json()[\"nonce\"])))\n\n        nonce = current_nonce + 1\n\n        print(f\"signing message with nonce {nonce}: {msg}, account={wallet.address}\")\n\n        if isinstance(wallet, ClefAccount):\n            return nonce, str(sign_with_clef(f\"{msg}{nonce}\", wallet))\n\n        return nonce, str(sign_with_key(f\"{msg}{nonce}\", wallet._private_key.hex()))\n\n    @staticmethod\n    @enforce_types\n    def get_url(config_dict: dict) -> str:\n        \"\"\"\n        Return the DataProvider component url.\n\n        :return: Url, str\n        \"\"\"\n        return _remove_slash(config_dict.get(\"PROVIDER_URL\"))\n\n    @staticmethod\n    @enforce_types\n    def get_service_endpoints(provider_uri: str) -> Dict[str, List[str]]:\n        \"\"\"\n        Return the service endpoints from the provider URL.\n        \"\"\"\n        provider_info = DataServiceProviderBase._http_method(\n            \"get\", url=provider_uri\n        ).json()\n\n        return provider_info[\"serviceEndpoints\"]\n\n    @staticmethod\n    @enforce_types\n    def get_c2d_environments(provider_uri: str, chain_id: int) -> Optional[str]:\n        \"\"\"\n        Return the provider address\n        \"\"\"\n        try:\n            method, envs_endpoint = DataServiceProviderBase.build_endpoint(\n                \"computeEnvironments\", provider_uri, {\"chainId\": chain_id}\n            )\n            environments = DataServiceProviderBase._http_method(\n                method, url=envs_endpoint\n            ).json()\n\n            if str(chain_id) not in environments:\n                logger.warning(\n                    \"You might be using an older provider. ocean.py can not verify the chain id.\"\n                )\n                return environments\n\n            return environments[str(chain_id)]\n        except (requests.exceptions.RequestException, KeyError):\n            pass\n\n        return []\n\n    @staticmethod\n    @enforce_types\n    def get_provider_address(provider_uri: str, chain_id: int) -> Optional[str]:\n        \"\"\"\n        Return the provider address\n        \"\"\"\n        try:\n            provider_info = DataServiceProviderBase._http_method(\n                \"get\", provider_uri\n            ).json()\n\n            if \"providerAddress\" in provider_info:\n                logger.warning(\n                    \"You might be using an older provider. ocean.py can not verify the chain id.\"\n                )\n                return provider_info[\"providerAddress\"]\n\n            return provider_info[\"providerAddresses\"][str(chain_id)]\n        except requests.exceptions.RequestException:\n            pass\n\n        return None\n\n    @staticmethod\n    @enforce_types\n    def get_root_uri(service_endpoint: str) -> str:\n        provider_uri = service_endpoint\n\n        if \"/api\" in provider_uri:\n            i = provider_uri.find(\"/api\")\n            provider_uri = provider_uri[:i]\n\n        parts = provider_uri.split(\"/\")\n\n        if len(parts) < 2:\n            raise InvalidURL(f\"InvalidURL {service_endpoint}.\")\n\n        if parts[-2] == \"services\":\n            provider_uri = \"/\".join(parts[:-2])\n\n        result = _remove_slash(provider_uri)\n\n        if not result:\n            raise InvalidURL(f\"InvalidURL {service_endpoint}.\")\n\n        try:\n            root_result = \"/\".join(parts[0:3])\n            response = requests.get(root_result).json()\n        except (requests.exceptions.RequestException, JSONDecodeError):\n            raise InvalidURL(f\"InvalidURL {service_endpoint}.\")\n\n        if \"providerAddresses\" not in response:\n            if \"providerAddress\" in response:\n                logger.warning(\n                    \"You might be using an older provider. ocean.py can not verify the chain id.\"\n                )\n            else:\n                raise InvalidURL(\n                    f\"Invalid Provider URL {service_endpoint}, no providerAddresses.\"\n                )\n\n        return result\n\n    @staticmethod\n    @enforce_types\n    def is_valid_provider(provider_uri: str) -> bool:\n        try:\n            DataServiceProviderBase.get_root_uri(provider_uri)\n        except InvalidURL:\n            return False\n\n        return True\n\n    @staticmethod\n    @enforce_types\n    def build_endpoint(\n        service_name: str, provider_uri: str, params: Optional[dict] = None\n    ) -> Tuple[str, str]:\n        provider_uri = DataServiceProviderBase.get_root_uri(provider_uri)\n        service_endpoints = DataServiceProviderBase.get_service_endpoints(provider_uri)\n\n        method, url = service_endpoints[service_name]\n        url = urljoin(provider_uri, url)\n\n        if params:\n            req = PreparedRequest()\n            req.prepare_url(url, params)\n            url = req.url\n\n        return method, url\n\n    @staticmethod\n    @enforce_types\n    def write_file(\n        response: Response,\n        destination_folder: Union[str, bytes, os.PathLike],\n        index: int,\n    ) -> None:\n        \"\"\"\n        Write the response content in a file in the destination folder.\n        :param response: Response\n        :param destination_folder: Destination folder, string\n        :param index: file index\n        :return: None\n        \"\"\"\n        if response.status_code != 200:\n            logger.warning(f\"consume failed: {response.reason}\")\n            return\n\n        with open(os.path.join(destination_folder, f\"file{index}\"), \"wb\") as f:\n            for chunk in response.iter_content(chunk_size=4096):\n                f.write(chunk)\n        logger.info(f\"Saved downloaded file in {f.name}\")\n\n    @staticmethod\n    @enforce_types\n    def _validate_content_disposition(header: str) -> bool:\n        pattern = re.compile(r\"\\\\|\\.\\.|/\")\n        return not bool(pattern.findall(header))\n\n    @staticmethod\n    @enforce_types\n    def _get_file_name(response: Response) -> Optional[str]:\n        try:\n            if not DataServiceProviderBase._validate_content_disposition(\n                response.headers.get(\"content-disposition\")\n            ):\n                logger.error(\n                    \"Invalid content disposition format. It was not possible to get the file name.\"\n                )\n                return None\n\n            return re.match(\n                r\"attachment;filename=(.+)\",\n                response.headers.get(\"content-disposition\"),\n            )[1]\n        except Exception as e:\n            logger.warning(f\"It was not possible to get the file name. {e}\")\n            return None\n\n    @staticmethod\n    @enforce_types\n    def _http_method(method: str, *args, **kwargs) -> Optional[Union[Mock, Response]]:\n        try:\n            return getattr(DataServiceProviderBase._http_client, method.lower())(\n                *args, **kwargs\n            )\n        except Exception:\n            logger.error(\n                f\"Error invoking http method {method}: args={str(args)}, kwargs={str(kwargs)}\"\n            )\n            raise\n\n    @staticmethod\n    @enforce_types\n    def check_response(\n        response,\n        endpoint_name: str,\n        endpoint: str,\n        payload: Union[Dict, bytes],\n        success_codes: Optional[List] = None,\n        exception_type=DataProviderException,\n    ):\n        if not response or not hasattr(response, \"status_code\"):\n            if isinstance(response, Response) and response.status_code == 400:\n                error = response.json().get(\n                    \"error\", response.json().get(\"errors\", \"unknown error\")\n                )\n\n                raise DataProviderException(f\"{endpoint_name} failed: {error}\")\n\n            response_content = getattr(response, \"content\", \"<none>\")\n\n            raise DataProviderException(\n                f\"Failed to get a response for request: {endpoint_name}={endpoint}, payload={payload}, response is {response_content}\"\n            )\n\n        if not success_codes:\n            success_codes = [200]\n\n        if response.status_code not in success_codes:\n            msg = (\n                f\"request failed at the {endpoint_name}\"\n                f\"{endpoint}, reason {response.text}, status {response.status_code}\"\n            )\n            logger.error(msg)\n            raise exception_type(msg)\n\n        return None\n\n\n@enforce_types\ndef urljoin(*args) -> str:\n    trailing_slash = \"/\" if args[-1].endswith(\"/\") else \"\"\n\n    return \"/\".join(map(lambda x: str(x).strip(\"/\"), args)) + trailing_slash\n\n\ndef _remove_slash(path: str) -> str:\n    path = path[:-1] if path.endswith(\"/\") else path\n    path = path[1:] if path.startswith(\"/\") else path\n\n    return path\n"
  },
  {
    "path": "ocean_lib/data_provider/data_encryptor.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n\"\"\"Provider module.\"\"\"\nimport json\nimport logging\nfrom typing import Union\n\nfrom enforce_typing import enforce_types\nfrom requests.models import Response\n\nfrom ocean_lib.data_provider.base import DataServiceProviderBase\nfrom ocean_lib.exceptions import OceanEncryptAssetUrlsError\n\nlogger = logging.getLogger(__name__)\n\n\nclass DataEncryptor(DataServiceProviderBase):\n    \"\"\"DataEncryptor class.\"\"\"\n\n    @staticmethod\n    @enforce_types\n    def encrypt(\n        objects_to_encrypt: Union[list, str, bytes, dict],\n        provider_uri: str,\n        chain_id: int,\n    ) -> Response:\n        if isinstance(objects_to_encrypt, dict):\n            data = json.dumps(objects_to_encrypt, separators=(\",\", \":\"))\n            payload = data.encode(\"utf-8\")\n        elif isinstance(objects_to_encrypt, str):\n            payload = objects_to_encrypt.encode(\"utf-8\")\n        else:\n            payload = objects_to_encrypt\n\n        method, encrypt_endpoint = DataServiceProviderBase.build_endpoint(\n            \"encrypt\", provider_uri, {\"chainId\": chain_id}\n        )\n\n        response = DataServiceProviderBase._http_method(\n            method,\n            encrypt_endpoint,\n            data=payload,\n            headers={\"Content-type\": \"application/octet-stream\"},\n        )\n\n        DataServiceProviderBase.check_response(\n            response,\n            \"encryptEndpoint\",\n            encrypt_endpoint,\n            payload,\n            [201],\n            OceanEncryptAssetUrlsError,\n        )\n\n        logger.info(\n            f\"Asset urls encrypted successfully, encrypted urls str: {response.text},\"\n            f\" encryptedEndpoint {encrypt_endpoint}\"\n        )\n\n        return response\n"
  },
  {
    "path": "ocean_lib/data_provider/data_service_provider.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n\"\"\"Provider module.\"\"\"\nimport json\nimport logging\nfrom json import JSONDecodeError\nfrom pathlib import Path\nfrom typing import Any, Dict, List, Optional, Union\n\nfrom enforce_typing import enforce_types\nfrom requests.models import PreparedRequest, Response\n\nfrom ocean_lib.agreements.service_types import ServiceTypes\nfrom ocean_lib.data_provider.base import DataServiceProviderBase\nfrom ocean_lib.data_provider.fileinfo_provider import FileInfoProvider\nfrom ocean_lib.http_requests.requests_session import get_requests_session\nfrom ocean_lib.models.compute_input import ComputeInput\nfrom ocean_lib.structures.algorithm_metadata import AlgorithmMetadata\n\nlogger = logging.getLogger(__name__)\n\n\nclass DataServiceProvider(DataServiceProviderBase):\n    \"\"\"DataServiceProvider class.\n\n    The main functions available are:\n    - consume_service\n    - run_compute_service (not implemented yet)\n    \"\"\"\n\n    _http_client = get_requests_session()\n    provider_info = None\n\n    @staticmethod\n    @enforce_types\n    def initialize(\n        did: str,\n        service: Any,  # Can not add Service typing due to enforce_type errors.\n        consumer_address: str,\n        userdata: Optional[Dict] = None,\n    ) -> Response:\n        method, initialize_endpoint = DataServiceProvider.build_endpoint(\n            \"initialize\", service.service_endpoint\n        )\n\n        payload = {\n            \"documentId\": did,\n            \"serviceId\": service.id,\n            \"consumerAddress\": consumer_address,\n        }\n\n        if userdata is not None:\n            userdata = json.dumps(userdata)\n            payload[\"userdata\"] = userdata\n\n        response = DataServiceProvider._http_method(\n            method, url=initialize_endpoint, params=payload\n        )\n\n        DataServiceProviderBase.check_response(\n            response, \"initializeEndpoint\", initialize_endpoint, payload\n        )\n\n        logger.info(\n            f\"Service initialized successfully\"\n            f\" initializeEndpoint {initialize_endpoint}\"\n        )\n\n        return response\n\n    @staticmethod\n    @enforce_types\n    def initialize_compute(\n        datasets: List[Dict[str, Any]],\n        algorithm_data: Dict[str, Any],\n        service_endpoint,\n        consumer_address: str,\n        compute_environment: str,\n        valid_until: int,\n    ) -> Response:\n        \"\"\"This function initializes compute services.\n\n        To determine the Provider instance that will be called, we rely on the first dataset.\n        The first dataset is also required to have a compute service.\n        \"\"\"\n        (\n            method,\n            initialize_compute_endpoint,\n        ) = DataServiceProvider.build_endpoint(\"initializeCompute\", service_endpoint)\n\n        payload = {\n            \"datasets\": datasets,\n            \"algorithm\": algorithm_data,\n            \"compute\": {\n                \"env\": compute_environment,\n                \"validUntil\": valid_until,\n            },\n            \"consumerAddress\": consumer_address,\n        }\n\n        response = DataServiceProvider._http_method(\n            method,\n            initialize_compute_endpoint,\n            data=json.dumps(payload),\n            headers={\"content-type\": \"application/json\"},\n        )\n\n        DataServiceProviderBase.check_response(\n            response, \"initializeComputeEndpoint\", initialize_compute_endpoint, payload\n        )\n\n        logger.info(\n            f\"Service initialized successfully\"\n            f\" initializeComputeEndpoint {initialize_compute_endpoint}\"\n        )\n\n        return response\n\n    @staticmethod\n    @enforce_types\n    def download(\n        did: str,\n        service: Any,  # Can not add Service typing due to enforce_type errors.\n        tx_id: Union[str, bytes],\n        consumer_wallet,\n        destination_folder: Union[str, Path],\n        index: Optional[int] = None,\n        userdata: Optional[Dict] = None,\n    ) -> None:\n        service_endpoint = service.service_endpoint\n        fileinfo_response = FileInfoProvider.fileinfo(did, service, userdata=userdata)\n\n        files = fileinfo_response.json()\n        indexes = range(len(files))\n        if index is not None:\n            assert isinstance(index, int), logger.error(\"index has to be an integer.\")\n            assert index >= 0, logger.error(\"index has to be 0 or a positive integer.\")\n            assert index < len(files), logger.error(\n                \"index can not be bigger than the number of files\"\n            )\n            indexes = [index]\n\n        method, download_endpoint = DataServiceProvider.build_endpoint(\n            \"download\", service_endpoint\n        )\n\n        payload = {\n            \"documentId\": did,\n            \"serviceId\": service.id,\n            \"consumerAddress\": consumer_wallet.address,\n            \"transferTxId\": tx_id,\n        }\n\n        if userdata:\n            userdata = json.dumps(userdata)\n            payload[\"userdata\"] = userdata\n\n        for i in indexes:\n            payload[\"fileIndex\"] = i\n            payload[\"nonce\"], payload[\"signature\"] = DataServiceProvider.sign_message(\n                consumer_wallet, did, provider_uri=service_endpoint\n            )\n            response = DataServiceProvider._http_method(\n                method, url=download_endpoint, params=payload, stream=True, timeout=3\n            )\n\n            DataServiceProviderBase.check_response(\n                response, \"downloadEndpoint\", download_endpoint, payload\n            )\n\n            DataServiceProvider.write_file(response, destination_folder, i)\n\n            logger.info(\n                f\"DDO downloaded successfully\" f\" downloadEndpoint {download_endpoint}\"\n            )\n\n    @staticmethod\n    # @enforce_types omitted due to subscripted generics error\n    def start_compute_job(\n        dataset_compute_service: Any,  # Can not add Service typing due to enforce_type errors.\n        consumer,\n        dataset: ComputeInput,\n        compute_environment: str,\n        algorithm: Optional[ComputeInput] = None,\n        algorithm_meta: Optional[AlgorithmMetadata] = None,\n        algorithm_custom_data: Optional[str] = None,\n        input_datasets: Optional[List[ComputeInput]] = None,\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Start a compute job.\n\n        Either algorithm or algorithm_meta must be defined.\n\n        :param dataset_compute_service:\n        :param consumer: hex str the ethereum address of the consumer executing the compute job\n        :param dataset: ComputeInput dataset with a compute service\n        :param compute_environment: str compute environment id\n        :param algorithm: ComputeInput algorithm witha download service.\n        :param algorithm_meta: AlgorithmMetadata algorithm metadata\n        :param algorithm_custom_data: dict customizable algo parameters (ie. no of iterations, etc)\n        :param input_datasets: List[ComputeInput] additional input datasets\n        :return job_info dict\n        \"\"\"\n        assert (\n            algorithm or algorithm_meta\n        ), \"either an algorithm did or an algorithm meta must be provided.\"\n\n        assert (\n            hasattr(dataset_compute_service, \"type\")\n            and dataset_compute_service.type == ServiceTypes.CLOUD_COMPUTE\n        ), \"invalid compute service\"\n\n        payload = DataServiceProvider._prepare_compute_payload(\n            consumer=consumer,\n            dataset=dataset,\n            compute_environment=compute_environment,\n            dataset_compute_service=dataset_compute_service,\n            algorithm=algorithm,\n            algorithm_meta=algorithm_meta,\n            algorithm_custom_data=algorithm_custom_data,\n            input_datasets=input_datasets,\n        )\n        logger.info(f\"invoke start compute endpoint with this url: {payload}\")\n        method, compute_endpoint = DataServiceProvider.build_endpoint(\n            \"computeStart\", dataset_compute_service.service_endpoint\n        )\n        response = DataServiceProvider._http_method(\n            method,\n            compute_endpoint,\n            data=json.dumps(payload),\n            headers={\"content-type\": \"application/json\"},\n        )\n\n        logger.debug(\n            f\"got DataProvider execute response: {response.content} with status-code {response.status_code} \"\n        )\n\n        DataServiceProviderBase.check_response(\n            response, \"computeStartEndpoint\", compute_endpoint, payload, [200, 201]\n        )\n\n        try:\n            job_info = json.loads(response.content.decode(\"utf-8\"))\n            return job_info[0] if isinstance(job_info, list) else job_info\n\n        except KeyError as err:\n            logger.error(f\"Failed to extract jobId from response: {err}\")\n            raise KeyError(f\"Failed to extract jobId from response: {err}\")\n        except JSONDecodeError as err:\n            logger.error(f\"Failed to parse response json: {err}\")\n            raise\n\n    @staticmethod\n    @enforce_types\n    def stop_compute_job(\n        did: str,\n        job_id: str,\n        dataset_compute_service: Any,\n        consumer,  # Can not add Service typing due to enforce_type errors.\n    ) -> Dict[str, Any]:\n        \"\"\"\n\n        :param did: hex str the DDO id\n        :param job_id: str id of compute job that was returned from `start_compute_job`\n        :param dataset_compute_service:\n        :param consumer of the consumer's account\n\n        :return: bool whether the job was stopped successfully\n        \"\"\"\n        _, compute_stop_endpoint = DataServiceProvider.build_endpoint(\n            \"computeStop\", dataset_compute_service.service_endpoint\n        )\n        return DataServiceProvider._send_compute_request(\n            \"put\", did, job_id, compute_stop_endpoint, consumer\n        )\n\n    @staticmethod\n    @enforce_types\n    def delete_compute_job(\n        did: str,\n        job_id: str,\n        dataset_compute_service: Any,\n        consumer,  # Can not add Service typing due to enforce_type errors.\n    ) -> Dict[str, str]:\n        \"\"\"\n\n        :param did: hex str the DDO id\n        :param job_id: str id of compute job that was returned from `start_compute_job`\n        :param dataset_compute_service:\n        :param consumer of the consumer's account\n\n        :return: bool whether the job was deleted successfully\n        \"\"\"\n        method, compute_delete_endpoint = DataServiceProvider.build_endpoint(\n            \"computeDelete\", dataset_compute_service.service_endpoint\n        )\n        return DataServiceProvider._send_compute_request(\n            method, did, job_id, compute_delete_endpoint, consumer\n        )\n\n    @staticmethod\n    @enforce_types\n    def compute_job_status(\n        did: str,\n        job_id: str,\n        dataset_compute_service: Any,\n        consumer,  # Can not add Service typing due to enforce_type errors.\n    ) -> Dict[str, Any]:\n        \"\"\"\n\n        :param did: hex str the DDO id\n        :param job_id: str id of compute job that was returned from `start_compute_job`\n        :param dataset_compute_service:\n        :param consumer of the consumer's account\n\n        :return: dict of job_id to status info. When job_id is not provided, this will return\n            status for each job_id that exist for the did\n        \"\"\"\n        method, compute_status_endpoint = DataServiceProvider.build_endpoint(\n            \"computeStatus\", dataset_compute_service.service_endpoint\n        )\n        return DataServiceProvider._send_compute_request(\n            method, did, job_id, compute_status_endpoint, consumer\n        )\n\n    @staticmethod\n    @enforce_types\n    def compute_job_result(\n        job_id: str, index: int, dataset_compute_service: Any, consumer\n    ) -> bytes:\n        \"\"\"\n\n        :param job_id: str id of compute job that was returned from `start_compute_job`\n        :param index: int compute result index\n        :param dataset_compute_service:\n        :param consumer of the consumer's account\n\n        :return: dict of job_id to result urls.\n        \"\"\"\n\n        nonce, signature = DataServiceProvider.sign_message(\n            consumer,\n            f\"{consumer.address}{job_id}{str(index)}\",\n            provider_uri=dataset_compute_service.service_endpoint,\n        )\n\n        req = PreparedRequest()\n        params = {\n            \"signature\": signature,\n            \"nonce\": nonce,\n            \"jobId\": job_id,\n            \"index\": index,\n            \"consumerAddress\": consumer.address,\n        }\n\n        (method, compute_job_result_endpoint) = DataServiceProvider.build_endpoint(\n            \"computeResult\", dataset_compute_service.service_endpoint\n        )\n        req.prepare_url(compute_job_result_endpoint, params)\n        compute_job_result_file_url = req.url\n\n        logger.info(\n            f\"invoke the computeResult endpoint with this url: {compute_job_result_file_url}\"\n        )\n        response = DataServiceProvider._http_method(method, compute_job_result_file_url)\n\n        DataServiceProviderBase.check_response(\n            response, \"jobResultEndpoint\", compute_job_result_endpoint, params\n        )\n\n        return response.content\n\n    @staticmethod\n    @enforce_types\n    def compute_job_result_logs(\n        ddo: Any,\n        job_id: str,\n        dataset_compute_service: Any,\n        consumer,\n        log_type=\"output\",\n    ) -> List[Dict[str, Any]]:\n        \"\"\"\n\n        :param job_id: str id of compute job that was returned from `start_compute_job`\n        :param dataset_compute_service:\n        :param consumer of the consumer's account\n\n        :return: dict of job_id to result urls.\n        \"\"\"\n        status = DataServiceProvider.compute_job_status(\n            ddo.did, job_id, dataset_compute_service, consumer\n        )\n        function_result = []\n        for i in range(len(status[\"results\"])):\n            result = None\n            result_type = status[\"results\"][i][\"type\"]\n            result = DataServiceProvider.compute_job_result(\n                job_id, i, dataset_compute_service, consumer\n            )\n\n            # Extract algorithm output\n            if result_type == log_type:\n                function_result.append(result)\n\n        return function_result\n\n    @staticmethod\n    @enforce_types\n    def _send_compute_request(\n        http_method: str, did: str, job_id: str, service_endpoint: str, consumer\n    ) -> Dict[str, Any]:\n        nonce, signature = DataServiceProvider.sign_message(\n            consumer,\n            f\"{consumer.address}{job_id}{did}\",\n            provider_uri=service_endpoint,\n        )\n\n        req = PreparedRequest()\n        payload = {\n            \"consumerAddress\": consumer.address,\n            \"documentId\": did,\n            \"jobId\": job_id,\n            \"nonce\": nonce,\n            \"signature\": signature,\n        }\n        req.prepare_url(service_endpoint, payload)\n\n        logger.info(f\"invoke compute endpoint with this url: {req.url}\")\n        response = DataServiceProvider._http_method(http_method, req.url)\n        logger.debug(\n            f\"got provider execute response: {response.content} with status-code {response.status_code} \"\n        )\n\n        DataServiceProviderBase.check_response(\n            response, \"compute Endpoint\", req.url, payload\n        )\n\n        resp_content = json.loads(response.content.decode(\"utf-8\"))\n\n        if isinstance(resp_content, list):\n            return resp_content[0]\n\n        return resp_content\n\n    @staticmethod\n    # @enforce_types omitted due to subscripted generics error\n    def _prepare_compute_payload(\n        consumer,\n        dataset: ComputeInput,\n        dataset_compute_service: Any,  # Can not add Service typing due to enforce_type errors.\n        compute_environment: str,\n        algorithm: Optional[ComputeInput] = None,\n        algorithm_meta: Optional[AlgorithmMetadata] = None,\n        algorithm_custom_data: Optional[str] = None,\n        input_datasets: Optional[List[ComputeInput]] = None,\n    ) -> Dict[str, Any]:\n        assert (\n            algorithm or algorithm_meta\n        ), \"either an algorithm did or an algorithm meta must be provided.\"\n\n        if algorithm_meta:\n            assert isinstance(algorithm_meta, AlgorithmMetadata), (\n                f\"expecting a AlgorithmMetadata type \"\n                f\"for `algorithm_meta`, got {type(algorithm_meta)}\"\n            )\n\n        input_datasets = input_datasets if input_datasets else []\n        _input_datasets = []\n        for _input in input_datasets:\n            for req_key in [\"did\", \"transfer_tx_id\", \"service_id\"]:\n                assert getattr(\n                    _input, req_key\n                ), f\"The received dataset does not have a {req_key}.\"\n\n        # TODO: is the nonce correct here?\n        # Should it be the one from the compute service or a dataset?\n        nonce, signature = DataServiceProvider.sign_message(\n            consumer,\n            f\"{consumer.address}{dataset.did}\",\n            provider_uri=dataset_compute_service.service_endpoint,\n        )\n\n        payload = {\n            \"dataset\": {\n                \"documentId\": dataset.did,\n                \"serviceId\": dataset.service_id,\n                \"transferTxId\": dataset.transfer_tx_id,\n            },\n            \"environment\": compute_environment,\n            \"algorithm\": {},\n            \"signature\": signature,\n            \"nonce\": nonce,\n            \"consumerAddress\": consumer.address,\n            \"additionalInputs\": _input_datasets or [],\n        }\n\n        if dataset.userdata:\n            payload[\"dataset\"][\"userdata\"] = dataset.userdata\n\n        if algorithm:\n            payload.update(\n                {\n                    \"algorithm\": {\n                        \"documentId\": algorithm.did,\n                        \"serviceId\": algorithm.service_id,\n                        \"transferTxId\": algorithm.transfer_tx_id,\n                    }\n                }\n            )\n            if algorithm.userdata:\n                payload[\"algorithm\"][\"userdata\"] = algorithm.userdata\n            if algorithm_custom_data:\n                payload[\"algorithm\"][\"algocustomdata\"] = algorithm_custom_data\n        else:\n            payload[\"algorithm\"] = algorithm_meta.as_dictionary()\n\n        return payload\n\n    @staticmethod\n    @enforce_types\n    def check_single_file_info(url_object: dict, provider_uri: str) -> bool:\n        method, endpoint = DataServiceProvider.build_endpoint(\"fileinfo\", provider_uri)\n        response = DataServiceProvider._http_method(method, endpoint, json=url_object)\n\n        if response.status_code != 200:\n            return False\n\n        return any([file_info[\"valid\"] for file_info in response.json()])\n\n    @staticmethod\n    @enforce_types\n    def check_asset_file_info(\n        did: str, service_id: str, provider_uri: str, userdata: Optional[dict] = None\n    ) -> bool:\n        if not did:\n            return False\n\n        method, endpoint = DataServiceProvider.build_endpoint(\"fileinfo\", provider_uri)\n        data = {\"did\": did, \"serviceId\": service_id}\n\n        if userdata is not None:\n            data[\"userdata\"] = userdata\n\n        response = DataServiceProvider._http_method(method, endpoint, json=data)\n\n        if not response or response.status_code != 200:\n            return False\n\n        return any([file_info[\"valid\"] for file_info in response.json()])\n"
  },
  {
    "path": "ocean_lib/data_provider/fileinfo_provider.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n\"\"\"Provider module.\"\"\"\nimport logging\nfrom typing import Any, Optional\n\nfrom enforce_typing import enforce_types\nfrom requests.models import Response\n\nfrom ocean_lib.data_provider.base import DataServiceProviderBase\nfrom ocean_lib.http_requests.requests_session import get_requests_session\n\nlogger = logging.getLogger(__name__)\n\n\nclass FileInfoProvider(DataServiceProviderBase):\n    \"\"\"DataServiceProvider class.\n\n    The main functions available are:\n    - consume_service\n    - run_compute_service (not implemented yet)\n    \"\"\"\n\n    _http_client = get_requests_session()\n    provider_info = None\n\n    @staticmethod\n    @enforce_types\n    def fileinfo(\n        did: str,\n        service: Any,\n        with_checksum: bool = False,\n        userdata: Optional[dict] = None,\n    ) -> Response:  # Can not add Service typing due to enforce_type errors.\n        method, fileinfo_endpoint = DataServiceProviderBase.build_endpoint(\n            \"fileinfo\", service.service_endpoint\n        )\n\n        payload = {\"did\": did, \"serviceId\": service.id}\n\n        if userdata is not None:\n            payload[\"userdata\"] = userdata\n\n        if with_checksum:\n            payload[\"checksum\"] = 1\n\n        response = DataServiceProviderBase._http_method(\n            method, fileinfo_endpoint, json=payload\n        )\n\n        DataServiceProviderBase.check_response(\n            response, \"fileInfoEndpoint\", fileinfo_endpoint, payload\n        )\n\n        logger.info(\n            f\"Retrieved asset files successfully\"\n            f\" FileInfoEndpoint {fileinfo_endpoint} from did {did} with service id {service.id}\"\n        )\n        return response\n"
  },
  {
    "path": "ocean_lib/data_provider/test/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "ocean_lib/data_provider/test/conftest.py",
    "content": "from conftest_ganache import *\n"
  },
  {
    "path": "ocean_lib/data_provider/test/test_base.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom requests.models import Response\n\nfrom ocean_lib.data_provider.base import DataServiceProviderBase\n\n\ndef test_validate_content_disposition():\n    header = \"./../../my/relative/path\"\n    res = DataServiceProviderBase._validate_content_disposition(header)\n    assert not res\n\n    header = \"somehtml.html\"\n    res = DataServiceProviderBase._validate_content_disposition(header)\n    assert res\n\n\ndef test_get_file_name(caplog):\n    response = Response()\n\n    response.headers[\"content-disposition\"] = \"./../../my/relative/path\"\n    file_name = DataServiceProviderBase._get_file_name(response)\n    assert not file_name\n    assert (\n        \"Invalid content disposition format. It was not possible to get the file name.\"\n        in caplog.text\n    )\n\n    response.headers[\"content-disposition\"] = \"attachment;filename=somehtml.html\"\n    file_name = DataServiceProviderBase._get_file_name(response)\n    assert file_name == \"somehtml.html\"\n"
  },
  {
    "path": "ocean_lib/data_provider/test/test_data_service_provider.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport json\nfrom datetime import datetime, timedelta, timezone\nfrom unittest.mock import Mock\n\nimport ecies\nimport pytest\nfrom requests.exceptions import InvalidURL\nfrom requests.models import Response\nfrom web3.main import Web3\n\nfrom ocean_lib.agreements.service_types import ServiceTypes\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.data_provider.base import DataServiceProviderBase, urljoin\nfrom ocean_lib.data_provider.data_encryptor import DataEncryptor\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider as DataSP\nfrom ocean_lib.data_provider.fileinfo_provider import FileInfoProvider\nfrom ocean_lib.example_config import DEFAULT_PROVIDER_URL\nfrom ocean_lib.exceptions import DataProviderException, OceanEncryptAssetUrlsError\nfrom ocean_lib.http_requests.requests_session import get_requests_session\nfrom ocean_lib.models.compute_input import ComputeInput\nfrom ocean_lib.services.service import Service\nfrom tests.resources.ddo_helpers import (\n    get_first_service_by_type,\n    get_registered_asset_with_access_service,\n)\nfrom tests.resources.mocks.http_client_mock import (\n    TEST_SERVICE_ENDPOINTS,\n    HttpClientEmptyMock,\n    HttpClientEvilMock,\n    HttpClientNiceMock,\n)\n\n\n@pytest.fixture\ndef with_evil_client():\n    http_client = HttpClientEvilMock()\n    DataSP.set_http_client(http_client)\n    yield\n    DataSP.set_http_client(get_requests_session())\n\n\n@pytest.fixture\ndef with_nice_client():\n    http_client = HttpClientNiceMock()\n    DataSP.set_http_client(http_client)\n    yield\n    DataSP.set_http_client(get_requests_session())\n\n\n@pytest.fixture\ndef with_empty_client():\n    http_client = HttpClientEmptyMock()\n    DataSP.set_http_client(http_client)\n    yield\n    DataSP.set_http_client(get_requests_session())\n\n\ndef test_set_http_client(with_nice_client):\n    \"\"\"Tests that a custom http client can be set on the DataServiceProvider.\"\"\"\n    assert isinstance(DataSP.get_http_client(), HttpClientNiceMock)\n\n\n@pytest.mark.unit\ndef test_initialize_fails():\n    \"\"\"Tests failures of initialize endpoint.\"\"\"\n    mock_service = Service(\n        service_id=\"some_service_id\",\n        service_type=\"some_service_type\",\n        service_endpoint=\"http://mock/\",\n        datatoken=\"some_dt\",\n        files=\"some_files\",\n        timeout=0,\n    )\n    with pytest.raises(\n        InvalidURL, match=f\"InvalidURL {mock_service.service_endpoint}.\"\n    ):\n        DataSP.initialize(\n            \"some_did\",\n            mock_service,\n            \"some_consumer_address\",\n            userdata={\"test_dict_key\": \"test_dict_value\"},\n        )\n\n    mock_service.service_endpoint = DEFAULT_PROVIDER_URL\n    with pytest.raises(\n        DataProviderException,\n        match=\"initializeEndpoint failed\",\n    ):\n        DataSP.initialize(\n            \"some_did\",\n            mock_service,\n            \"some_consumer_address\",\n            userdata={\"test_dict_key\": \"test_dict_value\"},\n        )\n\n\n@pytest.mark.unit\ndef test_start_compute_job_fails_empty(consumer_wallet):\n    \"\"\"Tests failures of compute job from endpoint with empty response.\"\"\"\n    mock_service = Service(\n        service_id=\"some_service_id\",\n        service_type=\"compute\",\n        service_endpoint=\"http://mock/\",\n        datatoken=\"some_dt\",\n        files=\"some_files\",\n        timeout=0,\n        compute_values=dict(),\n    )\n\n    mock_ddo = DDO()\n    with pytest.raises(\n        InvalidURL, match=f\"InvalidURL {mock_service.service_endpoint}.\"\n    ):\n        DataSP.start_compute_job(\n            dataset_compute_service=mock_service,\n            consumer=consumer_wallet,\n            dataset=ComputeInput(mock_ddo, mock_service, \"tx_id\"),\n            compute_environment=\"some_env\",\n            algorithm=ComputeInput(DDO(), mock_service, \"tx_id\"),\n        )\n    mock_service.service_endpoint = DEFAULT_PROVIDER_URL\n    with pytest.raises(\n        DataProviderException, match=\"The dataset.documentId field is required.\"\n    ):\n        DataSP.start_compute_job(\n            dataset_compute_service=mock_service,\n            consumer=consumer_wallet,\n            dataset=ComputeInput(DDO(), mock_service, \"tx\"),\n            compute_environment=\"some_env\",\n            algorithm=ComputeInput(DDO(), mock_service, \"tx\"),\n        )\n\n\n@pytest.mark.unit\ndef test_send_compute_request_failure(with_evil_client, provider_wallet):\n    \"\"\"Tests failure of compute request from endpoint with non-200 response.\"\"\"\n    with pytest.raises(Exception):\n        DataSP._send_compute_request(\n            \"post\", \"some_did\", \"some_job_id\", \"http://mock/\", provider_wallet\n        )\n\n\n@pytest.mark.unit\ndef test_compute_job_result_fails(provider_wallet):\n    \"\"\"Tests failure of compute job starting.\"\"\"\n\n    mock_service = Service(\n        service_id=\"some_service_id\",\n        service_type=\"some_service_type\",\n        service_endpoint=\"http://mock\",\n        datatoken=\"some_dt\",\n        files=\"some_files\",\n        timeout=0,\n        compute_values=dict(),\n    )\n    with pytest.raises(\n        InvalidURL, match=f\"InvalidURL {mock_service.service_endpoint}.\"\n    ):\n        DataSP.compute_job_result(\"some_job_id\", 0, mock_service, provider_wallet)\n\n\n@pytest.mark.unit\ndef test_delete_job_result(provider_wallet):\n    \"\"\"Tests a failure & a success of compute job deletion.\"\"\"\n    mock_service = Service(\n        service_id=\"some_service_id\",\n        service_type=\"some_service_type\",\n        service_endpoint=\"http://mock/\",\n        datatoken=\"some_dt\",\n        files=\"some_files\",\n        timeout=0,\n        compute_values=dict(),\n    )\n\n    # Failure of compute job deletion.\n    with pytest.raises(\n        InvalidURL, match=f\"InvalidURL {mock_service.service_endpoint}.\"\n    ):\n        DataSP.delete_compute_job(\n            \"some_did\", \"some_job_id\", mock_service, provider_wallet\n        )\n\n    # Success of compute job deletion.\n    mock_service.service_endpoint = DEFAULT_PROVIDER_URL\n    DataSP.delete_compute_job(\"some_did\", \"some_job_id\", mock_service, provider_wallet)\n\n\n@pytest.mark.integration\ndef test_encrypt(provider_wallet, file1, file2):\n    \"\"\"Tests successful encrypt job.\"\"\"\n    key = provider_wallet._private_key.hex()\n    # Encrypt file objects\n    res = {\"files\": [file1.to_dict(), file2.to_dict()]}\n    result = DataEncryptor.encrypt(res, DEFAULT_PROVIDER_URL, 8996)\n    encrypted_files = result.content.decode(\"utf-8\")\n    assert result.status_code == 201\n    assert result.headers[\"Content-type\"] == \"text/plain\"\n    assert encrypted_files.startswith(\"0x\")\n\n    if isinstance(encrypted_files, str):\n        encrypted_files = Web3.to_bytes(hexstr=encrypted_files)\n    decrypted_document = ecies.decrypt(key, encrypted_files)\n    decrypted_document_string = decrypted_document.decode(\"utf-8\")\n    assert decrypted_document_string == json.dumps(res, separators=(\",\", \":\"))\n\n    # Encrypt a simple string\n    test_string = \"hello_world\"\n    encrypt_result = DataEncryptor.encrypt(test_string, DEFAULT_PROVIDER_URL, 8996)\n    encrypted_document = encrypt_result.content.decode(\"utf-8\")\n    assert result.status_code == 201\n    assert result.headers[\"Content-type\"] == \"text/plain\"\n    assert result.content.decode(\"utf-8\").startswith(\"0x\")\n\n    if isinstance(encrypted_document, str):\n        encrypted_document = Web3.to_bytes(hexstr=encrypted_document)\n    decrypted_document = ecies.decrypt(key, encrypted_document)\n    decrypted_document_string = decrypted_document.decode(\"utf-8\")\n    assert decrypted_document_string == test_string\n\n\n@pytest.mark.integration\ndef test_fileinfo_and_initialize(publisher_wallet, publisher_ocean, ocean_address):\n    \"\"\"Test both fileinfo and initialize to avoid publishing 2 assets.\"\"\"\n    _, _, ddo = get_registered_asset_with_access_service(\n        publisher_ocean, publisher_wallet, more_files=True\n    )\n\n    access_service = get_first_service_by_type(ddo, ServiceTypes.ASSET_ACCESS)\n\n    fileinfo_result = FileInfoProvider.fileinfo(\n        ddo.did, access_service, with_checksum=True\n    )\n    assert fileinfo_result.status_code == 200\n    files_info = fileinfo_result.json()\n    assert len(files_info) == 2\n    for file_index, file in enumerate(files_info):\n        assert file[\"index\"] == file_index\n        assert file[\"checksum\"]\n        assert file[\"valid\"] is True\n        matches = \"text/plain\" if file_index == 0 else \"text/xml\"\n        assert file[\"contentType\"] == matches\n\n    initialize_result = DataSP.initialize(\n        did=ddo.did,\n        service=access_service,\n        consumer_address=publisher_wallet.address,\n    )\n    assert initialize_result\n    assert initialize_result.status_code == 200\n    response_json = initialize_result.json()\n    assert response_json[\"providerFee\"][\"providerFeeAmount\"] == \"0\"\n    assert response_json[\"providerFee\"][\"providerFeeToken\"] == ocean_address\n\n\n@pytest.mark.unit\ndef test_invalid_file_name():\n    \"\"\"Tests that no filename is returned if attachment headers are found.\"\"\"\n    response = Mock(spec=Response)\n    response.headers = {\"no_good\": \"headers at all\"}\n    assert DataSP._get_file_name(response) is None\n\n\n@pytest.mark.integration\ndef test_expose_endpoints():\n    \"\"\"Tests that the DataServiceProvider exposes all service endpoints.\"\"\"\n    service_endpoints = TEST_SERVICE_ENDPOINTS\n    provider_uri = DEFAULT_PROVIDER_URL\n    valid_endpoints = DataSP.get_service_endpoints(provider_uri)\n    assert len(valid_endpoints) == len(service_endpoints)\n    assert [\n        valid_endpoints[key] for key in set(service_endpoints) & set(valid_endpoints)\n    ]\n\n\n@pytest.mark.integration\ndef test_c2d_environments():\n    \"\"\"Tests that the test ocean-compute env exists on the DataServiceProvider.\"\"\"\n    provider_uri = DEFAULT_PROVIDER_URL\n    c2d_envs = DataSP.get_c2d_environments(provider_uri, 8996)\n    c2d_env_ids = [elem[\"id\"] for elem in c2d_envs]\n    assert \"ocean-compute\" in c2d_env_ids, \"ocean-compute env not found.\"\n\n\n@pytest.mark.integration\ndef test_provider_address():\n    \"\"\"Tests that a provider address exists on the DataServiceProvider.\"\"\"\n    provider_uri = DEFAULT_PROVIDER_URL\n    provider_address = DataSP.get_provider_address(provider_uri, 8996)\n    assert provider_address, \"Failed to get provider address.\"\n\n    assert DataSP.get_provider_address(\"not a url\", 8996) is None\n\n\n@pytest.mark.integration\ndef test_get_root_uri():\n    \"\"\"Tests extraction of base URLs from various inputs.\"\"\"\n    uri = \"https://v4.provider.mainnet.oceanprotocol.com\"\n    assert DataSP.is_valid_provider(uri)\n    assert DataSP.get_root_uri(uri) == uri\n    assert DataSP.get_root_uri(\"http://localhost:8030\") == \"http://localhost:8030\"\n    assert (\n        DataSP.get_root_uri(\"http://localhost:8030/api/services/\")\n        == \"http://localhost:8030\"\n    )\n    assert DataSP.get_root_uri(\"http://localhost:8030/api\") == \"http://localhost:8030\"\n    assert (\n        DataSP.get_root_uri(\"http://localhost:8030/services\")\n        == \"http://localhost:8030/services\"\n    )\n    assert (\n        DataSP.get_root_uri(\"http://localhost:8030/services/download\")\n        == \"http://localhost:8030\"\n    )\n    assert (\n        DataSP.get_root_uri(\"http://localhost:8030/api/services\")\n        == \"http://localhost:8030\"\n    )\n    assert (\n        DataSP.get_root_uri(\"http://localhost:8030/api/services/\")\n        == \"http://localhost:8030\"\n    )\n\n    assert not DataSP.is_valid_provider(\"thisIsNotAnURL\")\n    with pytest.raises(InvalidURL):\n        DataSP.get_root_uri(\"thisIsNotAnURL\")\n\n    with pytest.raises(InvalidURL):\n        # URL is of correct format but unreachable\n        DataSP.get_root_uri(\"http://thisisaurl.but/itshouldnt\")\n\n    with pytest.raises(InvalidURL):\n        # valid URL, but no provider address\n        DataSP.get_root_uri(\"http://oceanprotocol.com\")\n\n    with pytest.raises(InvalidURL):\n        DataSP.get_root_uri(\"//\")\n\n\n@pytest.mark.integration\ndef test_build_endpoint():\n    \"\"\"Tests that service endpoints are correctly built from URL and service name.\"\"\"\n\n    def get_service_endpoints(_provider_uri=None):\n        _endpoints = TEST_SERVICE_ENDPOINTS.copy()\n        _endpoints.update({\"newEndpoint\": [\"GET\", \"/api/services/newthing\"]})\n        return _endpoints\n\n    original_func = DataServiceProviderBase.get_service_endpoints\n    DataServiceProviderBase.get_service_endpoints = get_service_endpoints\n\n    endpoints = get_service_endpoints()\n    uri = \"http://localhost:8030\"\n    method, endpnt = DataSP.build_endpoint(\"newEndpoint\", provider_uri=uri)\n    assert endpnt == urljoin(uri, endpoints[\"newEndpoint\"][1])\n\n    uri = \"http://localhost:8030/api/services/newthing\"\n    method, endpnt = DataSP.build_endpoint(\"download\", provider_uri=uri)\n    assert method == endpoints[\"download\"][0]\n    assert endpnt == urljoin(DataSP.get_root_uri(uri), endpoints[\"download\"][1])\n\n    DataSP.get_service_endpoints = original_func\n\n\n@pytest.mark.integration\ndef test_build_specific_endpoints():\n    \"\"\"Tests that a specific list of agreed endpoints is supported on the DataServiceProvider.\"\"\"\n    endpoints = TEST_SERVICE_ENDPOINTS\n\n    def get_service_endpoints(_provider_uri=None):\n        return TEST_SERVICE_ENDPOINTS.copy()\n\n    original_func = DataSP.get_service_endpoints\n    DataSP.get_service_endpoints = get_service_endpoints\n\n    provider_uri = DEFAULT_PROVIDER_URL\n    base_uri = DataSP.get_root_uri(DEFAULT_PROVIDER_URL)\n\n    assert (\n        DataSP.build_endpoint(\"encrypt\", provider_uri, {\"chainId\": 8996})[1]\n        == urljoin(base_uri, endpoints[\"encrypt\"][1]) + \"?chainId=8996\"\n    )\n\n    for key in [\n        \"fileinfo\",\n        \"download\",\n        \"initialize\",\n        \"initializeCompute\",\n        \"computeStatus\",\n        \"computeStart\",\n        \"computeStop\",\n        \"computeDelete\",\n    ]:\n        assert DataSP.build_endpoint(key, provider_uri)[1] == urljoin(\n            base_uri, endpoints[key][1]\n        )\n\n    DataSP.get_service_endpoints = original_func\n\n\n@pytest.mark.integration\ndef test_check_single_file_info():\n    assert DataSP.check_single_file_info(\n        {\"url\": \"http://www.google.com\", \"type\": \"url\"},\n        provider_uri=\"http://172.15.0.4:8030\",\n    )\n    assert not DataSP.check_single_file_info(\n        {\"url\": \"http://www.google.com\"}, provider_uri=\"http://172.15.0.4:8030\"\n    )\n    assert not DataSP.check_single_file_info({}, provider_uri=\"http://172.15.0.4:8030\")\n\n\n@pytest.mark.unit\ndef test_encrypt_failure():\n    \"\"\"Tests encrypt failures.\"\"\"\n    http_client = HttpClientEvilMock()\n    DataEncryptor.set_http_client(http_client)\n\n    with pytest.raises(OceanEncryptAssetUrlsError):\n        DataEncryptor.encrypt({}, DEFAULT_PROVIDER_URL, 8996)\n\n    http_client = HttpClientEmptyMock()\n    DataSP.set_http_client(http_client)\n\n    with pytest.raises(DataProviderException):\n        DataEncryptor.encrypt({}, DEFAULT_PROVIDER_URL, 8996)\n\n    DataSP.set_http_client(get_requests_session())\n\n\n@pytest.mark.unit\ndef test_fileinfo_failure():\n    \"\"\"Tests successful fileinfo failures.\"\"\"\n    service = Mock(spec=Service)\n    service.service_endpoint = \"http://172.15.0.4:8030\"\n    service.id = \"abc\"\n\n    http_client = HttpClientEvilMock()\n    DataSP.set_http_client(http_client)\n\n    with pytest.raises(DataProviderException):\n        FileInfoProvider.fileinfo(\"0xabc\", service)\n\n    http_client = HttpClientEmptyMock()\n    DataSP.set_http_client(http_client)\n\n    with pytest.raises(DataProviderException):\n        FileInfoProvider.fileinfo(\"0xabc\", service)\n\n    DataSP.set_http_client(get_requests_session())\n\n\n@pytest.mark.unit\ndef test_initialize_failure():\n    \"\"\"Tests initialize failures.\"\"\"\n    service = Mock(spec=Service)\n    service.service_endpoint = \"http://172.15.0.4:8030\"\n    service.id = \"abc\"\n\n    http_client = HttpClientEvilMock()\n    DataSP.set_http_client(http_client)\n\n    with pytest.raises(DataProviderException):\n        DataSP.initialize(\"0xabc\", service, \"0x\")\n\n    http_client = HttpClientEmptyMock()\n    DataSP.set_http_client(http_client)\n\n    with pytest.raises(DataProviderException):\n        DataSP.initialize(\"0xabc\", service, \"0x\")\n\n    DataSP.set_http_client(get_requests_session())\n\n\n@pytest.mark.unit\ndef test_initialize_compute_failure():\n    \"\"\"Tests initialize_compute failures.\"\"\"\n    service = Mock(spec=Service)\n    service.service_endpoint = \"http://172.15.0.4:8030\"\n    service.id = \"abc\"\n\n    ddo = Mock(spec=DDO)\n    ddo.did = \"0x0\"\n    compute_input = ComputeInput(ddo, service)\n\n    http_client = HttpClientEvilMock()\n    DataSP.set_http_client(http_client)\n    valid_until = int((datetime.now(timezone.utc) + timedelta(days=1)).timestamp())\n\n    with pytest.raises(\n        DataProviderException, match=\"request failed at the initializeComputeEndpoint\"\n    ):\n        DataSP.initialize_compute(\n            [compute_input.as_dictionary()],\n            compute_input.as_dictionary(),\n            service.service_endpoint,\n            \"0x0\",\n            \"test\",\n            valid_until,\n        )\n\n    http_client = HttpClientEmptyMock()\n    DataSP.set_http_client(http_client)\n\n    with pytest.raises(DataProviderException, match=\"Failed to get a response\"):\n        DataSP.initialize_compute(\n            [compute_input.as_dictionary()],\n            compute_input.as_dictionary(),\n            service.service_endpoint,\n            \"0x0\",\n            \"test\",\n            valid_until,\n        )\n\n    DataSP.set_http_client(get_requests_session())\n\n\n@pytest.mark.unit\ndef test_job_result_failure(consumer_wallet):\n    \"\"\"Tests compute job result failures.\"\"\"\n    service = Mock(spec=Service)\n    service.service_endpoint = \"http://172.15.0.4:8030\"\n    service.id = \"abc\"\n\n    http_client = HttpClientEvilMock()\n    DataSP.set_http_client(http_client)\n\n    with pytest.raises(DataProviderException):\n        DataSP.compute_job_result(\"0xabc\", 0, service, consumer_wallet)\n\n    DataSP.set_http_client(get_requests_session())\n\n\n@pytest.mark.unit\ndef test_check_asset_failure():\n    \"\"\"Tests check_asset_file_info failures.\"\"\"\n    assert DataSP.check_asset_file_info(\"\", \"\", DEFAULT_PROVIDER_URL) is False\n\n    http_client = HttpClientEvilMock()\n    DataSP.set_http_client(http_client)\n\n    assert DataSP.check_asset_file_info(\"test\", \"\", DEFAULT_PROVIDER_URL) is False\n\n    http_client = HttpClientEmptyMock()\n    DataSP.set_http_client(http_client)\n\n    assert DataSP.check_asset_file_info(\"test\", \"\", DEFAULT_PROVIDER_URL) is False\n\n    DataSP.set_http_client(get_requests_session())\n"
  },
  {
    "path": "ocean_lib/example_config.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\nimport copy\nimport logging\nimport os\nfrom pathlib import Path\nfrom typing import Optional\n\nimport addresses\nfrom enforce_typing import enforce_types\nfrom web3 import Web3\nfrom web3.exceptions import ExtraDataLengthError\n\nfrom ocean_lib.web3_internal.http_provider import get_web3_connection_provider\n\nlogging.basicConfig(level=logging.INFO)\n\nDEFAULT_METADATA_CACHE_URI = \"http://172.15.0.5:5000\"\nMETADATA_CACHE_URI = \"https://v4.aquarius.oceanprotocol.com\"\nDEFAULT_PROVIDER_URL = \"http://172.15.0.4:8030\"\n\nconfig_defaults = {\n    \"METADATA_CACHE_URI\": \"http://172.15.0.5:5000\",\n    \"PROVIDER_URL\": \"http://172.15.0.4:8030\",\n    \"DOWNLOADS_PATH\": \"consume-downloads\",\n}\n\nPROVIDER_PER_NETWORK = {\n    1: \"https://v4.provider.mainnet.oceanprotocol.com\",\n    5: \"https://v4.provider.goerli.oceanprotocol.com\",\n    10: \"https://v4.provider.oceanprotocol.com\",\n    56: \"https://v4.provider.bsc.oceanprotocol.com\",\n    137: \"https://v4.provider.polygon.oceanprotocol.com\",\n    246: \"https://v4.provider.energyweb.oceanprotocol.com\",\n    1285: \"https://v4.provider.moonriver.oceanprotocol.com\",\n    1287: \"https://v4.provider.moonbase.oceanprotocol.com\",\n    80001: \"https://v4.provider.mumbai.oceanprotocol.com\",\n    58008: \"https://v4.provider.oceanprotocol.com\",\n    8996: DEFAULT_PROVIDER_URL,\n}\n\nNAME_PER_NETWORK = {\n    1: \"mainnet\",\n    5: \"goerli\",\n    10: \"optimism\",\n    56: \"bsc\",\n    137: \"polygon\",\n    246: \"energyweb\",\n    1285: \"moonriver\",\n    1287: \"moonbase\",\n    80001: \"mumbai\",\n    58008: \"sepolia\",\n    8996: \"development\",\n}\n\n\ndef get_config_dict(network_url: Optional[str] = None) -> dict:\n    \"\"\"Return config dict containing default values for a given network.\n    Chain ID is determined by querying the RPC specified by network_url.\n    \"\"\"\n    if not network_url:\n        network_url = \"http://localhost:8545\"\n\n    config_dict = copy.deepcopy(config_defaults)\n    config_dict[\"web3_instance\"] = get_web3(network_url)\n    config_dict[\"CHAIN_ID\"] = config_dict[\"web3_instance\"].eth.chain_id\n\n    chain_id = config_dict[\"CHAIN_ID\"]\n    if chain_id not in PROVIDER_PER_NETWORK:\n        raise ValueError(\"The chain id for the specific RPC could not be fetched!\")\n\n    config_dict[\"PROVIDER_URL\"] = PROVIDER_PER_NETWORK[chain_id]\n    config_dict[\"NETWORK_NAME\"] = NAME_PER_NETWORK[chain_id]\n\n    if chain_id != 8996:\n        config_dict[\"METADATA_CACHE_URI\"] = METADATA_CACHE_URI\n\n    if os.getenv(\"ADDRESS_FILE\"):\n        base_file = os.getenv(\"ADDRESS_FILE\")\n        address_file = os.path.expanduser(base_file)\n    elif chain_id == 8996:\n        # this is auto-created when barge is run\n        base_file = \"~/.ocean/ocean-contracts/artifacts/address.json\"\n        address_file = os.path.expanduser(base_file)\n    else:\n        # `contract_addresses` comes from \"ocean-contracts\" pypi library,\n        # a JSON blob holding addresses of contract deployments, per network\n        address_file = (\n            Path(os.path.join(addresses.__file__, \"..\", \"address.json\"))\n            .expanduser()\n            .resolve()\n        )\n    assert os.path.exists(address_file), f\"Could not find address_file={address_file}.\"\n\n    config_dict[\"ADDRESS_FILE\"] = address_file\n\n    return config_dict\n\n\n@enforce_types\ndef get_web3(network_url: str) -> Web3:\n    \"\"\"\n    Return a web3 instance connected via the given network_url.\n    Adds POA middleware when connecting to the Rinkeby Testnet.\n    A note about using the `rinkeby` testnet:\n    Web3 py has an issue when making some requests to `rinkeby`\n    - the issue is described here: https://github.com/ethereum/web3.py/issues/549\n    - and the fix is here: https://web3py.readthedocs.io/en/latest/middleware.html#geth-style-proof-of-authority\n    \"\"\"\n    provider = get_web3_connection_provider(network_url)\n    web3 = Web3(provider)\n\n    try:\n        web3.eth.get_block(\"latest\")\n    except ExtraDataLengthError:\n        from web3.middleware import geth_poa_middleware\n\n        web3.middleware_onion.inject(geth_poa_middleware, layer=0)\n\n    web3.strict_bytes_type_checking = False\n\n    return web3\n"
  },
  {
    "path": "ocean_lib/exceptions.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n\nclass OceanEncryptAssetUrlsError(Exception):\n    \"\"\"Error invoking the encrypt endpoint.\"\"\"\n\n\nclass InsufficientBalance(Exception):\n    \"\"\"The token balance is insufficient.\"\"\"\n\n\nclass AquariusError(Exception):\n    \"\"\"Error invoking an Aquarius metadata service endpoint.\"\"\"\n\n\nclass VerifyTxFailed(Exception):\n    \"\"\"Transaction verification failed.\"\"\"\n\n\nclass TransactionFailed(Exception):\n    \"\"\"Transaction has failed.\"\"\"\n\n\nclass DataProviderException(Exception):\n    \"\"\"Exception from Provider endpoints.\"\"\"\n"
  },
  {
    "path": "ocean_lib/http_requests/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "ocean_lib/http_requests/requests_session.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom enforce_typing import enforce_types\nfrom requests.adapters import HTTPAdapter\nfrom requests.sessions import Session\n\n\n@enforce_types\nclass TimeoutHTTPAdapter(HTTPAdapter):\n    def __init__(self, *args, **kwargs):\n        self.timeout = 30\n        if \"timeout\" in kwargs:\n            self.timeout = kwargs[\"timeout\"]\n            del kwargs[\"timeout\"]\n        super().__init__(*args, **kwargs)\n\n    def send(self, request, **kwargs):\n        # timeout = kwargs.get(\"timeout\")\n        # if timeout is None:\n        kwargs[\"timeout\"] = self.timeout\n        return super().send(request, **kwargs)\n\n\ndef get_requests_session() -> Session:\n    \"\"\"\n    Set connection pool maxsize and block value to avoid `connection pool full` warnings.\n\n    :return: requests session\n    \"\"\"\n    session = Session()\n    session.mount(\n        \"http://\",\n        TimeoutHTTPAdapter(\n            pool_connections=25,\n            pool_maxsize=25,\n            pool_block=True,\n            max_retries=1,\n            timeout=30,\n        ),\n    )\n    session.mount(\n        \"https://\",\n        TimeoutHTTPAdapter(\n            pool_connections=25,\n            pool_maxsize=25,\n            pool_block=True,\n            max_retries=1,\n            timeout=30,\n        ),\n    )\n    return session\n"
  },
  {
    "path": "ocean_lib/models/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "ocean_lib/models/compute_input.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom typing import Dict, Optional, Union\n\nfrom enforce_typing import enforce_types\n\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.services.service import Service\nfrom ocean_lib.web3_internal.constants import ZERO_ADDRESS\n\n\nclass ComputeInput:\n    @enforce_types\n    def __init__(\n        self,\n        ddo: DDO,\n        service: Service,\n        transfer_tx_id: Union[str, bytes] = None,\n        userdata: Optional[Dict] = None,\n        consume_market_order_fee_token: Optional[str] = None,\n        consume_market_order_fee_amount: Optional[int] = None,\n    ) -> None:\n        \"\"\"Initialise and validate arguments.\"\"\"\n        assert ddo and service is not None, \"bad argument values.\"\n\n        if userdata:\n            assert isinstance(userdata, dict), \"Userdata must be a dictionary.\"\n\n        self.ddo = ddo\n        self.did = ddo.did\n        self.transfer_tx_id = transfer_tx_id\n        self.service = service\n        self.service_id = service.id\n        self.userdata = userdata\n        self.consume_market_order_fee_token = (\n            consume_market_order_fee_token\n            if consume_market_order_fee_token\n            else ZERO_ADDRESS\n        )\n        self.consume_market_order_fee_amount = (\n            consume_market_order_fee_amount if consume_market_order_fee_amount else 0\n        )\n\n    @enforce_types\n    def as_dictionary(self) -> Dict[str, Union[str, Dict]]:\n        res = {\n            \"documentId\": self.did,\n            \"serviceId\": self.service_id,\n        }\n\n        if self.userdata:\n            res[\"userdata\"] = self.userdata\n\n        if self.transfer_tx_id:\n            res[\"transferTxId\"] = self.transfer_tx_id\n\n        return res\n"
  },
  {
    "path": "ocean_lib/models/data_nft.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport json\nfrom base64 import b64encode\nfrom enum import IntEnum, IntFlag\nfrom typing import Optional\n\nfrom enforce_typing import enforce_types\nfrom web3 import Web3\nfrom web3.logs import DISCARD\n\nfrom ocean_lib.models.datatoken_base import DatatokenArguments, DatatokenBase\nfrom ocean_lib.ocean.util import (\n    create_checksum,\n    get_address_of_type,\n    get_args_object,\n    get_from_address,\n)\nfrom ocean_lib.web3_internal.constants import ZERO_ADDRESS\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\n\"\"\"\ndef addManager(address: str) -> None:\n    add a manager role to the address provided as a parameter\n    :param address: address of interest\n    :return: None\n\ndef addMultipleUsersToRoles(addresses: list, roles: list):\n    add multiple users to multiple roles, mapping each address to the corresponding role in the list\n    :param addresses: list of addresses\n    :param roles: list of roles\n    :return: None\n\ndef addTo725StoreList(address: str) -> None:\n    add a role for storing datatokens to the address provided as a parameter\n    :param address: address of interest\n    :return: None\n\ndef addToCreateERC20List(address: str) -> None:\n    add a role for deploying datatokens to the address provided as a parameter\n    :param address: address of interest\n    :return: None\n\ndef addToMetadataList(address: str) -> None:\n    add a role for updating metadata to the address provided as a parameter\n    :param address: address of interest\n    :return: None\n\ndef approve(dst: str, tokenId: int) -> None:\n    approve a token for address\n    :param address: destination address\n    :param tokenId: token Id\n    :return: None\n\ndef balance() -> int:\n    get token balance\n    :return: int\n\ndef balanceOf(address: str) -> int:\n    get token balance for specific address\n    :param address: address of interest\n    :return: int\n\ndef baseURI() -> str:\n    get token baseURI\n    :return: str\n\ndef cleanPermissions() -> None:\n    reset all permissions on token,\n    must include the tx_dict with the publisher as transaction sender\n    :return: None\n\ndef executeCall(operation: int, dst: str, value: int, data: bytes) -> str:\n    execute call\n    :param operation: int representation of the operation to run\n    :param dst: destination account address\n    :param value: amount in wei\n    :param data: operation data\n    :return: transaction tx_id\n\ndef getApproved(tokenId: int) -> None:\n    get approved address for a specific token Id\n    :param tokenId: token Id\n    :return: address\n\ndef getData(key: bytes32) -> bytes:\n    get data assigned on token for specific key\n    :param key: key of interest\n    :return: value\n\ndef getId() -> int:\n    get token Id\n    :return: id\n\ndef getMetaData() -> tuple:\n    get medatadata of token\n    :return: tuple of decryptor url, decryptor address, metadata state, hasMetaData)\n\ndef getPermissions(user: str) -> tuple:\n    get user permissions\n    :param user: account address of interest\n    :return: tuple of boolean values for manager role, deployer role, metadata updater role, store role\n\ndef getTokensList() -> list:\n    get list of ERC20 tokens deployed on this NFT\n    :return: list of token addresses\n\ndef hasMetaData() -> bool:\n    :return: True if token has metadata, False otherwise\n\ndef isApprovedForAll(owner: str, operator: str) -> bool:\n    returns if the operator is allowed to manage all the assets of owner.\n    :param owner: address of owner\n    :param operator: address of operator\n    :return: bool\n\ndef metaDataDecryptorAddress() -> str:\n    :return: address of metadata decryptor\n\ndef metaDataDecryptorUrl() -> str:\n    :return: url of metadata decryptor\n\ndef metaDataState() -> int:\n    :return: metadata state according to convention\n\ndef name() -> str:\n    :return: name of token\n\ndef ownerOf(tokenId: int) -> str:\n    get owner for a specific token Id\n    :param tokenId: token Id\n    :return: owner address\n\ndef removeFrom725StoreList(address: str) -> None:\n    remove role for storing datatokens to the address provided as a parameter\n    :param address: address of interest\n    :return: None\n\ndef removeFromCreateERC20List(address: str) -> None:\n    remove role for deploying datatokens to the address provided as a parameter\n    :param address: address of interest\n    :return: None\n\ndef removeFromMetadataList(address: str) -> None:\n    remove role for updating metadata to the address provided as a parameter\n    :param address: address of interest\n    :return: None\n\ndef removeManager(address: str) -> None:\n    remove manager role for the address provided as a parameter\n    :param address: address of interest\n    :return: None\n\ndef safeTransferFrom(from: str, to: str, token_id: int) -> TransactionReceipt:\n    transfer ownership from one address to another\n    :param from: address of current owner account\n    :param to: address of destination account\n    :param token_id: token Id\n    :return: TransactionReceipt\n\ndef setApprovalForAll(operator: str, bool approved) -> None:\n    approve or remove address as an operator for the token\n    :param operator: address of operator\n    :param approved: True for approval, False to revoke\n    :return: None\n\ndef setBaseURI(base_uri: str) -> None:\n    set token baseURI\n    :param base_uri:\n    :return: None\n\ndef setDataERC20(key: bytes, value: bytes) -> None:\n    set a key, value pair on the token\n    :param key:\n    :param bytes:\n    :return: None\n\ndef setMetaData(\n    metaDataState: int,\n    metaDataDecryptorUrl: str,\n    metaDataDecryptorAddress: str,\n    flags: bytes,\n    data: bytes,\n    metaDataHash: bytes,\n    metadataProofs: list\n) -> None:\n    set token metadata, must include tx_dict with an authorized metadata updater as the sender\n    :param metaDataState: metadata state as an int according to convention\n    :param metaDataDecryptorUrl: metadata decryptor url\n    :param metaDataDecryptorAddress: metadata decryptor address\n    :param flags: encrypt/compress flags\n    :param data: metadata (encoded as bytes)\n    :param metaDataHash: metadata hash\n    :param metadataProofs: list of tuples of valudator address and v, r, s signature values retrieved from validator\n    :return: None\n\ndef setMetaDataAndTokenURI(\n    metadataAndTokenURI: tuple,\n) -> None:\n    similar to setMetaData, set token metadata and token URI, must include tx_dict with an authorized metadata updater as the sender\n    :param metadataAndTokenURI: tuple of the form (state, decryptor url, decryptor address, flags, data, hash, tokenURI, proofs)\n    :return: None\n\ndef setMetaDataState(metaDataState: int) -> None:\n    set token metadata state, must include tx_dict with an authorized metadata updater as the sender\n    :param metaDataState: metadata state as an int according to convention\n    :return: None\n\ndef setNewData(key: bytes, value: bytes) -> None:\n    set a key, value pair on the token\n    :param key:\n    :param bytes:\n    :return: None\n\ndef setTokenURI(tokenURI: str) -> None:\n    set token URI, must include tx_dict with an authorized metadata updater as the sender\n    :param tokenURI: token URI\n    :return: None\n\ndef symbol() -> str:\n    :return: symbol of token\n\ndef tokenByIndex(index: int) -> int:\n    Returns a token ID at a given index of all the tokens stored by the contract\n    :param index: int, index of a token\n    :return: int id of the token\n\ndef tokenOfOwnerByIndex(owner: str, index: int) -> int:\n    Returns a token ID owned by owner at a given index of all the tokens stored by the contract\n    :param owner: owner address\n    :param index: int, index of a token\n    :return: int id of the token\n\ndef tokenURI() -> str:\n    :return: tokenURI of token\n\ndef totalSupply() -> int:\n    :return: total supply of token\n\ndef transferFrom(from: str, to: str, token_id: int) -> TransactionReceipt:\n    transfer ownership from one address to another\n    :param from: address of current owner account\n    :param to: address of destination account\n    :param token_id: token Id\n    :return: TransactionReceipt\n\ndef transferable() -> bool:\n    :return: True if token is transferable, False otherwise\n\ndef withdrawETH() -> None:\n    withdraws all available ETH into the owner account\n    :return: None\n\n\nThe following functions are wrapped with ocean.py helpers, but you can use the raw form if needed:\ncreateERC20\n\"\"\"\n\n\nclass DataNFTPermissions(IntEnum):\n    MANAGER = 0\n    DEPLOY_DATATOKEN = 1\n    UPDATE_METADATA = 2\n    STORE = 3\n\n\nclass MetadataState(IntEnum):\n    ACTIVE = 0\n    END_OF_LIFE = 1\n    DEPRECATED = 2\n    REVOKED = 3\n    TEMPORARILY_DISABLED = 4\n\n\nclass Flags(IntFlag):\n    PLAIN = 0\n    COMPRESSED = 1\n    ENCRYPTED = 2\n\n    def to_byte(self):\n        return self.to_bytes(1, \"big\")\n\n\n@enforce_types\nclass DataNFT(ContractBase):\n    CONTRACT_NAME = \"ERC721Template\"\n\n    def create_datatoken(self, tx_dict, *args, **kwargs) -> DatatokenBase:\n        datatoken_args = get_args_object(args, kwargs, DatatokenArguments)\n\n        return datatoken_args.create_datatoken(self, tx_dict)\n\n    def calculate_did(self):\n        chain_id = self.config_dict[\"CHAIN_ID\"]\n        return f\"did:op:{create_checksum(self.address + str(chain_id))}\"\n\n    def set_data(self, field_label: str, field_value: str, tx_dict: dict):\n        \"\"\"Set key/value data via ERC725, with strings for key/value\"\"\"\n        field_label_hash = Web3.keccak(text=field_label)  # to keccak256 hash\n        field_value_bytes = field_value.encode()  # to array of bytes\n        tx = self.setNewData(field_label_hash, field_value_bytes, tx_dict)\n        return tx\n\n    def get_data(self, field_label: str) -> str:\n        \"\"\"Get key/value data via ERC725, with strings for key/value\"\"\"\n        field_label_hash = Web3.keccak(text=field_label)  # to keccak256 hash\n        field_value_hex = self.getData(field_label_hash)\n        field_value = field_value_hex.decode(\"ascii\")\n        return field_value\n\n\nclass DataNFTArguments:\n    def __init__(\n        self,\n        name: str,\n        symbol: str,\n        template_index: Optional[int] = 1,\n        additional_datatoken_deployer: Optional[str] = None,\n        additional_metadata_updater: Optional[str] = None,\n        uri: Optional[str] = None,\n        transferable: Optional[bool] = None,\n        owner: Optional[str] = None,\n    ):\n        \"\"\"\n        :param name: str name of data NFT if creating a new one\n        :param symbol: str symbol of data NFT  if creating a new one\n        :param template_index: int template index of the data NFT, by default is 1.\n        :param additional_datatoken_deployer: str address of an additional ERC20 deployer.\n        :param additional_metadata_updater: str address of an additional metadata updater.\n        :param uri: str URL of the data NFT.\n        \"\"\"\n        self.name = name\n        self.symbol = symbol or name\n        self.template_index = template_index\n        self.additional_datatoken_deployer = (\n            additional_datatoken_deployer or ZERO_ADDRESS\n        )\n        self.additional_metadata_updater = additional_metadata_updater or ZERO_ADDRESS\n        self.uri = uri or self.get_default_token_uri()\n        self.transferable = transferable or True\n        self.owner = owner\n\n    def get_default_token_uri(self):\n        data = {\n            \"name\": self.name,\n            \"symbol\": self.symbol,\n            \"background_color\": \"141414\",\n            \"image_data\": \"data:image/svg+xml,%3Csvg viewBox='0 0 99 99' fill='undefined' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23ff409277' d='M0,99L0,29C9,24 19,19 31,19C42,18 55,23 67,25C78,26 88,23 99,21L99,99Z'/%3E%3Cpath fill='%23ff4092bb' d='M0,99L0,43C9,45 18,47 30,48C41,48 54,46 66,45C77,43 88,43 99,43L99,99Z'%3E%3C/path%3E%3Cpath fill='%23ff4092ff' d='M0,99L0,78C10,75 20,72 31,71C41,69 53,69 65,70C76,70 87,72 99,74L99,99Z'%3E%3C/path%3E%3C/svg%3E\",\n        }\n\n        return b\"data:application/json;base64,\" + b64encode(\n            json.dumps(data, separators=(\",\", \":\")).encode(\"utf-8\")\n        )\n\n    def deploy_contract(self, config_dict, tx_dict) -> DataNFT:\n        from ocean_lib.models.data_nft_factory import (  # isort:skip\n            DataNFTFactoryContract,\n        )\n\n        address = get_address_of_type(config_dict, DataNFTFactoryContract.CONTRACT_NAME)\n        data_nft_factory = DataNFTFactoryContract(config_dict, address)\n\n        wallet_address = get_from_address(tx_dict)\n\n        receipt = data_nft_factory.deployERC721Contract(\n            self.name,\n            self.symbol,\n            self.template_index,\n            self.additional_metadata_updater,\n            self.additional_datatoken_deployer,\n            self.uri,\n            self.transferable,\n            self.owner or wallet_address,\n            tx_dict,\n        )\n\n        registered_event = (\n            data_nft_factory.contract.events.NFTCreated().process_receipt(\n                receipt, errors=DISCARD\n            )[0]\n        )\n        data_nft_address = registered_event.args.newTokenAddress\n\n        return DataNFT(config_dict, data_nft_address)\n"
  },
  {
    "path": "ocean_lib/models/data_nft_factory.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom typing import List, Optional, Union\n\nfrom enforce_typing import enforce_types\nfrom web3.exceptions import BadFunctionCallOutput\nfrom web3.logs import DISCARD\n\nfrom ocean_lib.models.data_nft import DataNFT, DataNFTArguments\nfrom ocean_lib.models.datatoken_base import DatatokenBase\nfrom ocean_lib.models.erc721_token_factory_base import ERC721TokenFactoryBase\nfrom ocean_lib.models.fixed_rate_exchange import FixedRateExchange, OneExchange\nfrom ocean_lib.ocean.util import get_address_of_type, get_args_object, get_from_address\nfrom ocean_lib.structures.abi_tuples import MetadataProof, OrderData, ReuseOrderData\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\n\"\"\"\ndef balance() -> int:\n    get token balance\n    :return: int\n\ndef getCurrentNFTCount() -> int:\n    get current NFT count\n    :return: int\n\ndef getCurrentNFTTemplateCount() -> int:\n    get current NFT template count (should be always 1 in current ocean.py)\n    :return: int\n\ndef getCurrentTemplateCount() -> int:\n    get current ERC20 template count (should be always 2 in current ocean.py)\n    :return: int\n\ndef getCurrentTokenCount() -> int:\n    get current ERC20 token count\n    :return: int\n\ndef getNFTTemplate(index: int) -> tuple:\n    get NFT template details for specific index\n    :param index: index of the NFT template\n    :return: tuple of the form (address, valid), where address is the\n    template address and valid is a boolean value indicating template existence\n\ndef getTokenTemplate(index: int) -> tuple:\n    get ERC20 template details for specific index\n    :param index: index of the ERC20 template\n    :return: if template exists, tuple of the form (address, True), where\n    address is the template address; otherwise throws an exception\n\ndef owner() -> str:\n    get owner address of the contract\n    :return: str\n\n\nThe following functions are wrapped with ocean.py helpers, but you can use the raw form if needed:\ncreateNftWithErc20\ncreateNftWithErc20WithDispenser\ncreateNftWithErc20WithFixedRate\ncreateNftWithMetaData\ncreateToken\ndeployERC721Contract\nerc20List\nerc721List\nreuseMultipleTokenOrder\nstartMultipleTokenOrder\n\"\"\"\n\n\nclass DataNFTFactoryContract(ERC721TokenFactoryBase):\n    CONTRACT_NAME = \"ERC721Factory\"\n\n    @enforce_types\n    def verify_nft(self, nft_address: str) -> bool:\n        \"\"\"Checks that a token was registered.\"\"\"\n        data_nft_contract = DataNFT(self.config_dict, nft_address)\n        try:\n            data_nft_contract.getId()\n            return True\n        except BadFunctionCallOutput:\n            return False\n\n    def create(self, tx_dict, *args, **kwargs):\n        data_nft_args = get_args_object(args, kwargs, DataNFTArguments)\n\n        return data_nft_args.deploy_contract(self.config_dict, tx_dict)\n\n    @enforce_types\n    def start_multiple_token_order(self, orders: List[OrderData], tx_dict: dict) -> str:\n        \"\"\"An order contains the following keys:\n\n        - tokenAddress, str\n        - consumer, str\n        - serviceIndex, int\n        - providerFeeAddress, str\n        - providerFeeToken, str\n        - providerFeeAmount (in Wei), int\n        - providerData, bytes\n        - v, int\n        - r, bytes\n        - s, bytes\n        \"\"\"\n        for order in orders:\n            order._replace(\n                token_address=ContractBase.to_checksum_address(order.token_address)\n            )\n            order._replace(consumer=ContractBase.to_checksum_address(order.consumer))\n            provider_fees = list(order.provider_fees)\n            provider_fees[0] = ContractBase.to_checksum_address(order.provider_fees[0])\n            provider_fees[1] = ContractBase.to_checksum_address(order.provider_fees[1])\n            order._replace(provider_fees=tuple(provider_fees))\n            consume_fees = list(order.consume_fees)\n            consume_fees[0] = ContractBase.to_checksum_address(order.consume_fees[0])\n            consume_fees[1] = ContractBase.to_checksum_address(order.consume_fees[1])\n            order._replace(consume_fees=tuple(consume_fees))\n\n        return self.startMultipleTokenOrder(orders, tx_dict)\n\n    @enforce_types\n    def reuse_multiple_token_order(\n        self, reuse_orders: List[ReuseOrderData], tx_dict: dict\n    ) -> str:\n        for order in reuse_orders:\n            order._replace(\n                token_address=ContractBase.to_checksum_address(order.token_address)\n            )\n            provider_fees = list(order.provider_fees)\n            provider_fees[0] = ContractBase.to_checksum_address(order.provider_fees[0])\n            provider_fees[1] = ContractBase.to_checksum_address(order.provider_fees[1])\n\n        return self.reuseMultipleTokenOrder(reuse_orders, tx_dict)\n\n    @enforce_types\n    def create_with_erc20(\n        self,\n        data_nft_args,\n        datatoken_args,\n        tx_dict: dict,\n    ) -> str:\n        wallet_address = get_from_address(tx_dict)\n        receipt = self.createNftWithErc20(\n            (\n                data_nft_args.name,\n                data_nft_args.symbol,\n                data_nft_args.template_index,\n                data_nft_args.uri,\n                data_nft_args.transferable,\n                ContractBase.to_checksum_address(data_nft_args.owner or wallet_address),\n            ),\n            (\n                datatoken_args.template_index,\n                [datatoken_args.name, datatoken_args.symbol],\n                [\n                    ContractBase.to_checksum_address(\n                        datatoken_args.minter or wallet_address\n                    ),\n                    ContractBase.to_checksum_address(\n                        datatoken_args.fee_manager or wallet_address\n                    ),\n                    ContractBase.to_checksum_address(\n                        datatoken_args.publish_market_order_fees.address\n                    ),\n                    ContractBase.to_checksum_address(\n                        datatoken_args.publish_market_order_fees.token\n                    ),\n                ],\n                [datatoken_args.cap, datatoken_args.publish_market_order_fees.amount],\n                datatoken_args.bytess,\n            ),\n            tx_dict,\n        )\n\n        registered_nft_event = self.contract.events.NFTCreated().process_receipt(\n            receipt, errors=DISCARD\n        )[0]\n        data_nft_address = registered_nft_event.args.newTokenAddress\n        data_nft_token = DataNFT(self.config_dict, data_nft_address)\n\n        registered_token_event = self.contract.events.TokenCreated().process_receipt(\n            receipt, errors=DISCARD\n        )[0]\n        datatoken_address = registered_token_event.args.newTokenAddress\n        datatoken = DatatokenBase.get_typed(self.config_dict, datatoken_address)\n\n        return data_nft_token, datatoken\n\n    @enforce_types\n    def create_with_erc20_and_fixed_rate(\n        self,\n        data_nft_args,\n        datatoken_args,\n        fixed_price_args,\n        tx_dict: dict,\n    ) -> str:\n        wallet_address = get_from_address(tx_dict)\n\n        receipt = self.createNftWithErc20WithFixedRate(\n            (\n                data_nft_args.name,\n                data_nft_args.symbol,\n                data_nft_args.template_index,\n                data_nft_args.uri,\n                data_nft_args.transferable,\n                ContractBase.to_checksum_address(data_nft_args.owner or wallet_address),\n            ),\n            (\n                datatoken_args.template_index,\n                [datatoken_args.name, datatoken_args.symbol],\n                [\n                    ContractBase.to_checksum_address(\n                        datatoken_args.minter or wallet_address\n                    ),\n                    ContractBase.to_checksum_address(\n                        datatoken_args.fee_manager or wallet_address\n                    ),\n                    ContractBase.to_checksum_address(\n                        datatoken_args.publish_market_order_fees.address\n                    ),\n                    ContractBase.to_checksum_address(\n                        datatoken_args.publish_market_order_fees.token\n                    ),\n                ],\n                [datatoken_args.cap, datatoken_args.publish_market_order_fees.amount],\n                datatoken_args.bytess,\n            ),\n            fixed_price_args.to_tuple(self.config_dict, tx_dict),\n            tx_dict,\n        )\n\n        registered_nft_event = self.contract.events.NFTCreated().process_receipt(\n            receipt, errors=DISCARD\n        )[0]\n        data_nft_address = registered_nft_event.args.newTokenAddress\n        data_nft_token = DataNFT(self.config_dict, data_nft_address)\n\n        registered_token_event = self.contract.events.TokenCreated().process_receipt(\n            receipt, errors=DISCARD\n        )[0]\n        datatoken_address = registered_token_event.args.newTokenAddress\n        datatoken = DatatokenBase.get_typed(self.config_dict, datatoken_address)\n\n        registered_fixed_rate_event = (\n            self.contract.events.NewFixedRate().process_receipt(\n                receipt, errors=DISCARD\n            )[0]\n        )\n        exchange_id = registered_fixed_rate_event.args.exchangeId\n        fixed_rate_exchange = FixedRateExchange(\n            self.config_dict, get_address_of_type(self.config_dict, \"FixedPrice\")\n        )\n        exchange = OneExchange(fixed_rate_exchange, exchange_id)\n\n        return data_nft_token, datatoken, exchange\n\n    @enforce_types\n    def create_with_erc20_and_dispenser(\n        self,\n        data_nft_args,\n        datatoken_args,\n        dispenser_args,\n        tx_dict: dict,\n    ) -> str:\n        wallet_address = get_from_address(tx_dict)\n\n        receipt = self.createNftWithErc20WithDispenser(\n            (\n                data_nft_args.name,\n                data_nft_args.symbol,\n                data_nft_args.template_index,\n                data_nft_args.uri,\n                data_nft_args.transferable,\n                ContractBase.to_checksum_address(data_nft_args.owner or wallet_address),\n            ),\n            (\n                datatoken_args.template_index,\n                [datatoken_args.name, datatoken_args.symbol],\n                [\n                    ContractBase.to_checksum_address(\n                        datatoken_args.minter or wallet_address\n                    ),\n                    ContractBase.to_checksum_address(\n                        datatoken_args.fee_manager or wallet_address\n                    ),\n                    ContractBase.to_checksum_address(\n                        datatoken_args.publish_market_order_fees.address\n                    ),\n                    ContractBase.to_checksum_address(\n                        datatoken_args.publish_market_order_fees.token\n                    ),\n                ],\n                [datatoken_args.cap, datatoken_args.publish_market_order_fees.amount],\n                datatoken_args.bytess,\n            ),\n            dispenser_args.to_tuple(self.config_dict),\n            tx_dict,\n        )\n\n        registered_nft_event = self.contract.events.NFTCreated().process_receipt(\n            receipt, errors=DISCARD\n        )[0]\n        data_nft_address = registered_nft_event.args.newTokenAddress\n        data_nft_token = DataNFT(self.config_dict, data_nft_address)\n\n        registered_token_event = self.contract.events.TokenCreated().process_receipt(\n            receipt, errors=DISCARD\n        )[0]\n        datatoken_address = registered_token_event.args.newTokenAddress\n        datatoken = DatatokenBase.get_typed(self.config_dict, datatoken_address)\n\n        registered_dispenser_event = (\n            self.contract.events.DispenserCreated().process_receipt(\n                receipt, errors=DISCARD\n            )[0]\n        )\n        assert registered_dispenser_event.args.datatokenAddress == datatoken_address\n\n        return data_nft_token, datatoken\n\n    @enforce_types\n    def create_with_metadata(\n        self,\n        data_nft_args,\n        metadata_state: int,\n        metadata_decryptor_url: str,\n        metadata_decryptor_address: bytes,\n        metadata_flags: bytes,\n        metadata_data: Union[str, bytes],\n        metadata_data_hash: Union[str, bytes],\n        metadata_proofs: List[MetadataProof],\n        tx_dict: dict,\n    ) -> str:\n        wallet_address = get_from_address(tx_dict)\n\n        receipt = self.createNftWithMetaData(\n            (\n                data_nft_args.name,\n                data_nft_args.symbol,\n                data_nft_args.template_index,\n                data_nft_args.uri,\n                data_nft_args.transferable,\n                ContractBase.to_checksum_address(data_nft_args.owner or wallet_address),\n            ),\n            (\n                metadata_state,\n                metadata_decryptor_url,\n                metadata_decryptor_address,\n                metadata_flags,\n                metadata_data,\n                metadata_data_hash,\n                metadata_proofs,\n            ),\n            tx_dict,\n        )\n        registered_nft_event = self.contract.events.NFTCreated().process_receipt(\n            receipt, errors=DISCARD\n        )[0]\n        data_nft_address = registered_nft_event.args.newTokenAddress\n        data_nft_token = DataNFT(self.config_dict, data_nft_address)\n\n        return data_nft_token\n\n    @enforce_types\n    def search_exchange_by_datatoken(\n        self,\n        fixed_rate_exchange: FixedRateExchange,\n        datatoken: str,\n        exchange_owner: Optional[str] = None,\n    ) -> list:\n        datatoken_contract = DatatokenBase.get_typed(self.config_dict, datatoken)\n        exchange_addresses_and_ids = datatoken_contract.getFixedRates()\n        return (\n            exchange_addresses_and_ids\n            if exchange_owner is None\n            else [\n                exchange_address_and_id\n                for exchange_address_and_id in exchange_addresses_and_ids\n                if fixed_rate_exchange.getExchange(exchange_address_and_id[1])[0]\n                == exchange_owner\n            ]\n        )\n\n    @enforce_types\n    def get_token_address(self, receipt):\n        event = self.contract.events.NFTCreated().process_receipt(\n            receipt, errors=DISCARD\n        )[0]\n        return event.args.newTokenAddress\n\n    @enforce_types\n    def check_datatoken(self, datatoken_address: str) -> bool:\n        return self.erc20List(datatoken_address)\n\n    @enforce_types\n    def check_nft(self, nft_address: str) -> bool:\n        return self.erc721List(nft_address) == nft_address\n"
  },
  {
    "path": "ocean_lib/models/datatoken1.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport logging\nfrom typing import Any, Optional\n\nfrom enforce_typing import enforce_types\n\nfrom ocean_lib.models.datatoken_base import DatatokenBase, TokenFeeInfo\nfrom ocean_lib.ocean.util import from_wei, get_from_address, to_wei\nfrom ocean_lib.web3_internal.constants import ZERO_ADDRESS\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\nchecksum_addr = ContractBase.to_checksum_address\nlogger = logging.getLogger(\"ocean\")\n\n\"\"\"\ndef addMinter(address: str) -> None:\n    add a minter for the datatoken\n    :param address: address of interest\n    :return: None\n\ndef addPaymentManager(address: str) -> None:\n    add a payment manager for the datatoken\n    :param address: address of interest\n    :return: None\n\ndef allowance(owner_addr: str, spender_addr: str) -> int:\n    get token allowance for spender from owner\n    :param owner_addr: address of owner\n    :param spender_addr: address of owner\n    :return: int allowance\n\ndef approve(address: str, amount: int) -> None:\n    approve tokens for a specific address in the given amount\n    :param address: address of interest\n    :param amount: amount in int\n    :return: None\n\ndef authERC20(index: int) -> tuple:\n    get user permissions on ERC20 for specific index\n    :param index: index of interest\n    :return: tuple of tuples of boolean values for minter role, payment manager role\n\ndef balance() -> int:\n    get token balance\n    :return: int\n\ndef balanceOf(address: str) -> int:\n    get token balance for specific address\n    :param address: address of interest\n    :return: int\n\ndef burn(amount: int) -> None:\n    burn a specific amount of tokens\n    :param amount: amount in int\n    :return: None\n\ndef burnFrom(address: str, amount: int) -> None:\n    burn a specific amount of tokens from an account\n    :param address: address of the burner account\n    :param amount: amount in int\n    :return: None\n\ndef cap() -> int:\n    get token cap\n    :return: int\n\ndef cleanPermissions() -> None:\n    reset all permissions on token,\n    must include the tx_dict with the publisher as transaction sender\n    :return: None\n\ndef decimals() -> int:\n    get token decimals\n    :return: int\n\ndef decreaseAllowance(address: str, amount: int) -> None:\n    decrease the allowance for an address by a specific amount\n    :param address: address of the account\n    :param amount: amount to subtract in int\n    :return: None\n\ndef getERC721Address() -> str:\n    get address of ERC721 token\n    :return: str\n\ndef getId() -> int:\n    get token Id\n    :return: id\n\ndef getPaymentCollector() -> str:\n    get payment collector address\n    :return: address of payment collector\n\ndef getPermissions(user: str) -> tuple:\n    get user permissions\n    :param user: account address of interest\n    :return: tuple of boolean values for minter role, payment manager role\n\ndef increaseAllowance(address: str, amount: int) -> None:\n    increase the allowance for an address by a specific amount\n    :param address: address of the account\n    :param amount: amount to add in int\n    :return: None\n\ndef isERC20Deployer(address: str) -> bool:\n    returns whether an address has ERC20 Deployer role\n    :param address: address of interest\n    :return: bool\n\ndef isMinter(address: str) -> bool:\n    returns whether an address has minter role\n    :param address: address of interest\n    :return: bool\n\ndef mint(address: str, amount: int) -> None:\n    mints am amount of tokens for the given address,\n    requires tx_dict with a datatoken minter as the sender\n    returns whether an address has minter role\n    :param address: address of interest\n    :param amount: amount to mint\n    :return: None\n\ndef name() -> str:\n    :return: name of token\n\ndef removeMinter(address: str) -> None:\n    remove minter role for the datatoken\n    :param address: address of interest\n    :return: None\n\ndef removePaymentManager(address: str) -> None:\n    remove payment manager role for the datatoken\n    :param address: address of interest\n    :return: None\n\ndef setData(key: bytes, value: bytes) -> None:\n    set a key, value pair on the token\n    :param key:\n    :param bytes:\n    :return: None\n\ndef setPaymentCollector(address: str) -> None:\n    set payment collector address\n    :param address: address of payment collector\n    :return: None\n\ndef setPublishingMarketFee(address: str, token: str, amount: int) -> None:\n    set publishing market fee\n    :param address: address of the intended receiver\n    :param token: address of the token to receive fees in\n    :param amount: amount of intended fee\n    :return: None\n\ndef symbol() -> str:\n    :return: symbol of token\n\ndef totalSupply() -> int:\n    :return: total supply of token\n\ndef transfer(to: str, amount: int) -> None:\n    transfer an amount of tokens from transaction sender to address,\n    requires tx_dict with a datatoken minter as the sender\n    :param to: address of destination account\n    :param amount: amount to transfer\n    :return: None\n\ndef transferFrom(from: str, to: str, amount: int) -> None:\n    transfer an amount of tokens from one address to another,\n    requires tx_dict with a datatoken minter as the sender\n    :param from: address of current owner account\n    :param to: address of destination account\n    :param amount: amount to transfer\n    :return: None\n\ndef withdrawETH() -> None:\n    withdraws all available ETH into the owner account\n    :return: None\n\n\nThe following functions are wrapped with ocean.py helpers, but you can use the raw form if needed:\ncreateDispenser\ncreateFixedRate\ngetDispensers\ngetFixedRates\ngetPublishingMarketFee\nreuseOrder\nstartOrder\n\"\"\"\n\n\nclass Datatoken1(DatatokenBase):\n    CONTRACT_NAME = \"ERC20Template\"\n\n    BASE = 10**18\n    BASE_COMMUNITY_FEE_PERCENTAGE = BASE / 1000\n    BASE_MARKET_FEE_PERCENTAGE = BASE / 1000\n\n    # ===========================================================================\n    # consume\n\n    def dispense_and_order(\n        self,\n        provider_fees: dict,\n        tx_dict: dict,\n        consumer: Optional[str] = None,\n        service_index: int = 1,\n        consume_market_fees=None,\n    ) -> str:\n        if not consumer:\n            consumer = get_from_address(tx_dict)\n\n        if not consume_market_fees:\n            consume_market_fees = TokenFeeInfo()\n\n        buyer_addr = get_from_address(tx_dict)\n\n        bal = from_wei(self.balanceOf(buyer_addr))\n        if bal < 1.0:\n            dispensers = self.get_dispensers()\n\n            assert dispensers, \"there are no dispensers for this datatoken\"\n            dispenser = dispensers[0]\n\n            # catch key failure modes\n            st = dispenser.status(self.address)\n            active, allowedSwapper = st[0], st[6]\n            if not active:\n                raise ValueError(\"No active dispenser for datatoken\")\n            if allowedSwapper not in [ZERO_ADDRESS, buyer_addr]:\n                raise ValueError(f\"Not allowed. allowedSwapper={allowedSwapper}\")\n\n            # Try to dispense. If other issues, they'll pop out\n            dispenser.dispense(self.address, to_wei(1), buyer_addr, tx_dict)\n\n        return self.start_order(\n            consumer=ContractBase.to_checksum_address(consumer),\n            service_index=service_index,\n            provider_fees=provider_fees,\n            consume_market_fees=consume_market_fees,\n            tx_dict=tx_dict,\n        )\n\n    @enforce_types\n    def buy_DT_and_order(\n        self,\n        provider_fees: dict,\n        exchange: Any,\n        tx_dict: dict,\n        consumer: Optional[str] = None,\n        service_index: int = 1,\n        consume_market_fees=None,\n    ) -> str:\n        if not consumer:\n            consumer = get_from_address(tx_dict)\n\n        exchanges = self.get_exchanges()\n        assert exchanges, \"there are no fixed rate exchanges for this datatoken\"\n\n        # import now, to avoid circular import\n        from ocean_lib.models.fixed_rate_exchange import OneExchange\n\n        if not consume_market_fees:\n            consume_market_fees = TokenFeeInfo()\n\n        if not isinstance(exchange, OneExchange):\n            exchange = exchanges[0]\n\n        exchange.buy_DT(\n            datatoken_amt=to_wei(1),\n            consume_market_fee_addr=consume_market_fees.address,\n            consume_market_fee=consume_market_fees.amount,\n            tx_dict=tx_dict,\n        )\n\n        return self.start_order(\n            consumer=ContractBase.to_checksum_address(consumer),\n            service_index=service_index,\n            provider_fees=provider_fees,\n            consume_market_fees=consume_market_fees,\n            tx_dict=tx_dict,\n        )\n"
  },
  {
    "path": "ocean_lib/models/datatoken2.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom typing import Any, Optional, Union\n\nfrom enforce_typing import enforce_types\n\nfrom ocean_lib.models.datatoken_base import DatatokenBase, TokenFeeInfo\nfrom ocean_lib.ocean.util import get_from_address, to_wei\nfrom ocean_lib.web3_internal.constants import ZERO_ADDRESS\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\nchecksum_addr = ContractBase.to_checksum_address\n\n\"\"\"\nDatatoken2 retains all the functions from Datatoken model.\n\nThe different functions are redundant (wrapped by ocean.py in helpers):\nbuyFromDispenserAndOrder\nbuyFromFreAndOrder\n\"\"\"\n\n\nclass Datatoken2(DatatokenBase):\n    CONTRACT_NAME = \"ERC20TemplateEnterprise\"\n\n    @enforce_types\n    def buy_DT_and_order(\n        self,\n        provider_fees: dict,\n        exchange: Any,\n        tx_dict: dict,\n        consumer: Optional[str] = None,\n        service_index: int = 1,\n        consume_market_fees=None,\n        max_base_token_amount: Optional[Union[int, str]] = None,\n        consume_market_swap_fee_amount: Optional[Union[int, str]] = 0,\n        consume_market_swap_fee_address: Optional[str] = ZERO_ADDRESS,\n    ) -> str:\n        if not consumer:\n            consumer = get_from_address(tx_dict)\n\n        exchanges = self.get_exchanges()\n        assert exchanges, \"there are no fixed rate exchanges for this datatoken\"\n\n        # import now, to avoid circular import\n        from ocean_lib.models.fixed_rate_exchange import OneExchange\n\n        if not isinstance(exchange, OneExchange):\n            exchange = exchanges[0]\n\n        if not consume_market_fees:\n            consume_market_fees = TokenFeeInfo()\n\n        if not max_base_token_amount:\n            amt_needed = exchange.BT_needed(to_wei(1), consume_market_fees.amount)\n            max_base_token_amount = amt_needed\n\n        return self.buyFromFreAndOrder(\n            (\n                ContractBase.to_checksum_address(consumer),\n                service_index,\n                (\n                    checksum_addr(provider_fees[\"providerFeeAddress\"]),\n                    checksum_addr(provider_fees[\"providerFeeToken\"]),\n                    int(provider_fees[\"providerFeeAmount\"]),\n                    provider_fees[\"v\"],\n                    provider_fees[\"r\"],\n                    provider_fees[\"s\"],\n                    provider_fees[\"validUntil\"],\n                    provider_fees[\"providerData\"],\n                ),\n                consume_market_fees.to_tuple(),\n            ),\n            (\n                ContractBase.to_checksum_address(exchange.address),\n                exchange.exchange_id,\n                max_base_token_amount,\n                consume_market_swap_fee_amount,\n                ContractBase.to_checksum_address(consume_market_swap_fee_address),\n            ),\n            tx_dict,\n        )\n\n    @enforce_types\n    def dispense_and_order(\n        self,\n        provider_fees: dict,\n        tx_dict: dict,\n        consumer: Optional[str] = None,\n        service_index: int = 1,\n        consume_market_fees=None,\n    ) -> str:\n        if not consume_market_fees:\n            consume_market_fees = TokenFeeInfo()\n\n        if not consumer:\n            consumer = get_from_address(tx_dict)\n\n        dispensers = self.get_dispensers()\n        assert dispensers, \"there are no dispensers for this datatoken\"\n        dispenser = dispensers[0]\n\n        return self.buyFromDispenserAndOrder(\n            (\n                ContractBase.to_checksum_address(consumer),\n                service_index,\n                (\n                    checksum_addr(provider_fees[\"providerFeeAddress\"]),\n                    checksum_addr(provider_fees[\"providerFeeToken\"]),\n                    int(provider_fees[\"providerFeeAmount\"]),\n                    provider_fees[\"v\"],\n                    provider_fees[\"r\"],\n                    provider_fees[\"s\"],\n                    provider_fees[\"validUntil\"],\n                    provider_fees[\"providerData\"],\n                ),\n                consume_market_fees.to_tuple(),\n            ),\n            ContractBase.to_checksum_address(dispenser.address),\n            tx_dict,\n        )\n"
  },
  {
    "path": "ocean_lib/models/datatoken_base.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport logging\nfrom abc import ABC\nfrom enum import IntEnum\nfrom typing import Any, Dict, List, Optional, Tuple, Union\n\nfrom enforce_typing import enforce_types\nfrom web3.logs import DISCARD\nfrom web3.main import Web3\n\nfrom ocean_lib.agreements.service_types import ServiceTypes\nfrom ocean_lib.models.fixed_rate_exchange import OneExchange\nfrom ocean_lib.ocean.util import (\n    get_address_of_type,\n    get_args_object,\n    get_from_address,\n    get_ocean_token_address,\n    str_with_wei,\n)\nfrom ocean_lib.services.service import Service\nfrom ocean_lib.structures.file_objects import FilesType\nfrom ocean_lib.web3_internal.constants import MAX_UINT256, ZERO_ADDRESS\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\nchecksum_addr = ContractBase.to_checksum_address\nlogger = logging.getLogger(\"ocean\")\n\n\nclass TokenFeeInfo:\n    def __init__(\n        self,\n        address: Optional[str] = None,\n        token: Optional[str] = None,\n        amount: Optional[int] = 0,\n    ):\n        self.address = (\n            Web3.to_checksum_address(address.lower()) if address else ZERO_ADDRESS\n        )\n        self.token = Web3.to_checksum_address(token.lower()) if token else ZERO_ADDRESS\n\n        self.amount = amount\n\n    def to_tuple(self):\n        return (self.address, self.token, self.amount)\n\n    @classmethod\n    def from_tuple(cls, tup):\n        address, token, amount = tup\n        return cls(address, token, amount)\n\n    def __str__(self):\n        s = (\n            f\"TokenFeeInfo: \\n\"\n            f\"  address = {self.address}\\n\"\n            f\"  token = {self.token}\\n\"\n            f\"  amount = {str_with_wei(self.amount)}\\n\"\n        )\n        return s\n\n\nclass DatatokenArguments:\n    def __init__(\n        self,\n        name: Optional[str] = \"Datatoken 1\",\n        symbol: Optional[str] = \"DT1\",\n        template_index: Optional[int] = 1,\n        minter: Optional[str] = None,\n        fee_manager: Optional[str] = None,\n        publish_market_order_fees: Optional = None,\n        bytess: Optional[List[bytes]] = None,\n        services: Optional[list] = None,\n        files: Optional[List[FilesType]] = None,\n        consumer_parameters: Optional[List[Dict[str, Any]]] = None,\n        cap: Optional[int] = None,\n    ):\n        if template_index == 2 and not cap:\n            raise Exception(\"Cap is needed for Datatoken Template 2 token deployment.\")\n\n        self.cap = cap if template_index == 2 else MAX_UINT256\n\n        self.name = name\n        self.symbol = symbol\n        self.template_index = template_index\n        self.minter = minter\n        self.fee_manager = fee_manager\n        self.bytess = bytess or [b\"\"]\n        self.services = services\n        self.files = files\n        self.consumer_parameters = consumer_parameters\n\n        self.publish_market_order_fees = publish_market_order_fees or TokenFeeInfo()\n        self.set_default_fees_at_deploy = not publish_market_order_fees\n\n    def create_datatoken(self, data_nft, tx_dict, with_services=False):\n        config_dict = data_nft.config_dict\n        OCEAN_address = get_ocean_token_address(config_dict)\n        initial_list = data_nft.getTokensList()\n\n        wallet_address = get_from_address(tx_dict)\n\n        if self.set_default_fees_at_deploy:\n            self.publish_market_order_fees = TokenFeeInfo(\n                address=wallet_address, token=OCEAN_address\n            )\n\n        data_nft.createERC20(\n            self.template_index,\n            [self.name, self.symbol],\n            [\n                ContractBase.to_checksum_address(self.minter or wallet_address),\n                ContractBase.to_checksum_address(self.fee_manager or wallet_address),\n                self.publish_market_order_fees.address,\n                self.publish_market_order_fees.token,\n            ],\n            [self.cap, self.publish_market_order_fees.amount],\n            self.bytess,\n            tx_dict,\n        )\n\n        new_elements = [\n            item for item in data_nft.getTokensList() if item not in initial_list\n        ]\n        assert len(new_elements) == 1, \"new datatoken has no address\"\n\n        datatoken = DatatokenBase.get_typed(config_dict, new_elements[0])\n\n        logger.info(\n            f\"Successfully created datatoken with address \" f\"{datatoken.address}.\"\n        )\n\n        if with_services:\n            if not self.services:\n                self.services = [\n                    datatoken.build_access_service(\n                        service_id=\"0\",\n                        service_endpoint=config_dict.get(\"PROVIDER_URL\"),\n                        files=self.files,\n                        consumer_parameters=self.consumer_parameters,\n                    )\n                ]\n            else:\n                for service in self.services:\n                    service.datatoken = datatoken.address\n\n        return datatoken\n\n\nclass DatatokenRoles(IntEnum):\n    MINTER = 0\n    PAYMENT_MANAGER = 1\n\n\nclass DatatokenBase(ABC, ContractBase):\n    CONTRACT_NAME = \"ERC20Template\"\n\n    BASE = 10**18\n    BASE_COMMUNITY_FEE_PERCENTAGE = BASE / 1000\n    BASE_MARKET_FEE_PERCENTAGE = BASE / 1000\n\n    # ===========================================================================\n    # consume\n\n    @staticmethod\n    def get_typed(config, address):\n        from ocean_lib.models.datatoken1 import Datatoken1\n        from ocean_lib.models.datatoken2 import Datatoken2\n\n        datatoken = Datatoken1(config, address)\n\n        try:\n            template_id = datatoken.getId()\n        except Exception:\n            template_id = 1\n\n        return datatoken if template_id == 1 else Datatoken2(config, address)\n\n    @enforce_types\n    def start_order(\n        self,\n        consumer: str,\n        service_index: int,\n        provider_fees: dict,\n        tx_dict: dict,\n        consume_market_fees=None,\n    ) -> str:\n        if not consume_market_fees:\n            consume_market_fees = TokenFeeInfo()\n\n        return self.startOrder(\n            checksum_addr(consumer),\n            service_index,\n            (\n                checksum_addr(provider_fees[\"providerFeeAddress\"]),\n                checksum_addr(provider_fees[\"providerFeeToken\"]),\n                int(provider_fees[\"providerFeeAmount\"]),\n                provider_fees[\"v\"],\n                provider_fees[\"r\"],\n                provider_fees[\"s\"],\n                provider_fees[\"validUntil\"],\n                provider_fees[\"providerData\"],\n            ),\n            consume_market_fees.to_tuple(),\n            tx_dict,\n        )\n\n    @enforce_types\n    def reuse_order(\n        self,\n        order_tx_id: Union[str, bytes],\n        provider_fees: dict,\n        tx_dict: dict,\n    ) -> str:\n        return self.reuseOrder(\n            order_tx_id,\n            (\n                checksum_addr(provider_fees[\"providerFeeAddress\"]),\n                checksum_addr(provider_fees[\"providerFeeToken\"]),\n                int(provider_fees[\"providerFeeAmount\"]),\n                provider_fees[\"v\"],\n                provider_fees[\"r\"],\n                provider_fees[\"s\"],\n                provider_fees[\"validUntil\"],\n                provider_fees[\"providerData\"],\n            ),\n            tx_dict,\n        )\n\n    @enforce_types\n    def get_start_order_logs(\n        self,\n        consumer_address: Optional[str] = None,\n        from_block: Optional[int] = 0,\n        to_block: Optional[int] = \"latest\",\n    ) -> Tuple:\n        topic0 = self.get_event_signature(\"OrderStarted\")\n        topics = [topic0]\n        if consumer_address:\n            topic1 = f\"0x000000000000000000000000{consumer_address[2:].lower()}\"\n            topics = [topic0, topic1]\n\n        web3 = self.config_dict[\"web3_instance\"]\n        event_filter = web3.eth.filter(\n            {\n                \"topics\": topics,\n                \"toBlock\": to_block,\n                \"fromBlock\": from_block,\n            }\n        )\n\n        orders = []\n\n        for log in event_filter.get_all_entries():\n            receipt = web3.eth.wait_for_transaction_receipt(log.transactionHash)\n            processed_events = self.contract.events.OrderStarted().process_receipt(\n                receipt, errors=DISCARD\n            )\n            for processed_event in processed_events:\n                orders.append(processed_event)\n\n        return orders\n\n    # ======================================================================\n    # Priced data: fixed-rate exchange\n\n    @enforce_types\n    def create_exchange(\n        self, tx_dict: dict, *args, **kwargs\n    ) -> Union[OneExchange, tuple]:\n        \"\"\"\n        For this datatoken, create a single fixed-rate exchange (OneExchange).\n\n        This wraps the smart contract method Datatoken.createFixedRate()\n          with a simpler interface\n\n        Main params:\n        - rate - how many base tokens does 1 datatoken cost? In wei or str\n        - base_token_addr - e.g. OCEAN address\n        - tx_dict - e.g. {\"from\": alice_wallet}\n\n        Optional params, with good defaults\n        - owner_addr\n        - publish_market_fee_collector - fee going to publish mkt\n        - publish_market_fee - in wei or str, e.g. int(1e15) or \"0.001 ether\"\n          do they need to by supplied/allowed by participants like base token?\n        - allowed_swapper - if ZERO_ADDRESS, anyone can swap\n        - full_info - return just OneExchange, or (OneExchange, <other info>)\n\n        Return\n        - exchange - OneExchange\n        - (maybe) tx_receipt\n        \"\"\"\n        # import now, to avoid circular import\n        from ocean_lib.models.fixed_rate_exchange import ExchangeArguments, OneExchange\n\n        exchange_args = get_args_object(args, kwargs, ExchangeArguments)\n        args_tup = exchange_args.to_tuple(self.config_dict, tx_dict, self.decimals())\n\n        tx = self.createFixedRate(*(args_tup + (tx_dict,)))\n\n        event = self.contract.events.NewFixedRate().process_receipt(tx, errors=DISCARD)[\n            0\n        ]\n        exchange_id = event.args.exchangeId\n        FRE = self._FRE()\n        exchange = OneExchange(FRE, exchange_id)\n\n        return (exchange, tx) if kwargs.get(\"full_info\") else exchange\n\n    @enforce_types\n    def get_exchanges(self, only_active=True) -> list:\n        \"\"\"return List[OneExchange] - all the exchanges for this datatoken\"\"\"\n        # import now, to avoid circular import\n        from ocean_lib.models.fixed_rate_exchange import FixedRateExchange, OneExchange\n\n        exchanges = []\n        addrs_and_exchange_ids = self.getFixedRates()\n        exchanges = [\n            OneExchange(FixedRateExchange(self.config_dict, address), exchange_id)\n            for address, exchange_id in addrs_and_exchange_ids\n        ]\n\n        if not only_active:\n            return exchanges\n\n        return [exchange for exchange in exchanges if exchange.is_active()]\n\n    @enforce_types\n    def _FRE(self):\n        \"\"\"Return FixedRateExchange - global across all exchanges\"\"\"\n        # import now, to avoid circular import\n        from ocean_lib.models.fixed_rate_exchange import FixedRateExchange\n\n        FRE_addr = get_address_of_type(self.config_dict, \"FixedPrice\")\n        return FixedRateExchange(self.config_dict, FRE_addr)\n\n    # ======================================================================\n    # Free data: dispenser faucet\n\n    @enforce_types\n    def create_dispenser(self, tx_dict: dict, *args, **kwargs):\n        \"\"\"\n        For this datataken, create a dispenser faucet for free tokens.\n\n        This wraps the smart contract method Datatoken.createDispenser()\n          with a simpler interface.\n\n        :param: max_tokens - max # tokens to dispense, in wei\n        :param: max_balance - max balance of requester\n        :tx_dict: e.g. {\"from\": alice_wallet}\n        :return: tx\n        \"\"\"\n        # already created, so nothing to do\n        if self.dispenser_status().active:\n            return\n\n        from ocean_lib.models.dispenser import DispenserArguments  # isort:skip\n\n        dispenser_args = get_args_object(args, kwargs, DispenserArguments)\n        args_tup = dispenser_args.to_tuple(self.config_dict)\n\n        # do contract tx\n        tx = self.createDispenser(*(args_tup + (tx_dict,)))\n\n        return tx\n\n    @enforce_types\n    def get_dispensers(self, only_active=True) -> list:\n        \"\"\"return List[Dispenser] - all the dispensers for this datatoken\"\"\"\n        # import here to avoid circular import\n        from ocean_lib.models.dispenser import Dispenser\n\n        dispensers = []\n        addrs = self.getDispensers()\n        dispensers = [Dispenser(self.config_dict, address) for address in addrs]\n\n        if not only_active:\n            return dispensers\n\n        return [disp for disp in dispensers if disp.status(self.address)]\n\n    @enforce_types\n    def dispense(self, amount: Union[int, str], tx_dict: dict):\n        \"\"\"\n        Dispense free tokens via the dispenser faucet.\n\n        :param: amount - number of tokens to dispense, in wei\n        :tx_dict: e.g. {\"from\": alice_wallet}\n        :return: tx\n        \"\"\"\n        # args for contract tx\n        datatoken_addr = self.address\n        from_addr = get_from_address(tx_dict)\n\n        # do contract tx\n        tx = self._ocean_dispenser().dispense(\n            datatoken_addr, amount, from_addr, tx_dict\n        )\n        return tx\n\n    @enforce_types\n    def dispenser_status(self):\n        \"\"\":return: DispenserStatus object\"\"\"\n        # import here to avoid circular import\n        from ocean_lib.models.dispenser import DispenserStatus\n\n        status_tup = self._ocean_dispenser().status(self.address)\n        return DispenserStatus(status_tup)\n\n    @enforce_types\n    def _ocean_dispenser(self):\n        \"\"\":return: Dispenser object\"\"\"\n        # import here to avoid circular import\n        from ocean_lib.models.dispenser import Dispenser\n\n        dispenser_addr = get_address_of_type(self.config_dict, \"Dispenser\")\n        return Dispenser(self.config_dict, dispenser_addr)\n\n    @enforce_types\n    def build_access_service(\n        self,\n        service_id: str,\n        service_endpoint: str,\n        files: List[FilesType],\n        timeout: Optional[int] = 3600,\n        consumer_parameters=None,\n    ) -> Service:\n        return Service(\n            service_id=service_id,\n            service_type=ServiceTypes.ASSET_ACCESS,\n            service_endpoint=service_endpoint,\n            datatoken=self.address,\n            files=files,\n            timeout=timeout,\n            consumer_parameters=consumer_parameters,\n        )\n\n    def get_publish_market_order_fees(self):\n        return TokenFeeInfo.from_tuple(self.getPublishingMarketFee())\n\n    def get_from_pricing_schema_and_order(self, *args, **kwargs):\n        dispensers = self.dispenser_status().active\n        exchanges = self.get_exchanges()\n\n        if not dispensers and not exchanges:\n            raise ValueError(\"No pricing schemas found\")\n\n        if dispensers:\n            kwargs.pop(\"consume_market_swap_fee_amount\", None)\n            kwargs.pop(\"consume_market_swap_fee_address\", None)\n\n            return self.dispense_and_order(*args, **kwargs)\n\n        exchange = self.get_exchanges()[0]\n        kwargs[\"exchange\"] = exchange\n\n        consume_market_fees = kwargs.get(\"consume_market_fees\")\n        if not consume_market_fees:\n            consume_market_fees = TokenFeeInfo()\n\n        wallet = kwargs[\"tx_dict\"][\"from\"]\n        amt_needed = exchange.BT_needed(\n            Web3.to_wei(1, \"ether\"), consume_market_fees.amount\n        )\n        base_token = DatatokenBase.get_typed(\n            exchange._FRE.config_dict, exchange.details.base_token\n        )\n        base_token_balance = base_token.balanceOf(wallet.address)\n\n        if base_token_balance < amt_needed:\n            raise ValueError(\n                f\"Your token balance {base_token_balance} {base_token.symbol()} is not sufficient \"\n                f\"to execute the requested service. This service \"\n                f\"requires {amt_needed} {base_token.symbol()}.\"\n            )\n\n        if self.getId() == 1:\n            approve_address = exchange.address\n            kwargs.pop(\"consume_market_swap_fee_amount\", None)\n            kwargs.pop(\"consume_market_swap_fee_address\", None)\n        else:\n            approve_address = self.address\n            kwargs[\"max_base_token_amount\"] = amt_needed\n\n        base_token.approve(\n            approve_address,\n            amt_needed,\n            {\"from\": wallet},\n        )\n\n        return self.buy_DT_and_order(*args, **kwargs)\n\n\nclass MockERC20(DatatokenBase):\n    CONTRACT_NAME = \"MockERC20\"\n\n    def getId(self):\n        return 1\n\n\nclass MockOcean(DatatokenBase):\n    CONTRACT_NAME = \"MockOcean\"\n\n    def getId(self):\n        return 1\n"
  },
  {
    "path": "ocean_lib/models/df/df_rewards.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\n\nclass DFRewards(ContractBase):\n    CONTRACT_NAME = \"DFRewards\"\n"
  },
  {
    "path": "ocean_lib/models/df/df_strategy_v1.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\n\nclass DFStrategyV1(ContractBase):\n    CONTRACT_NAME = \"DFStrategyV1\"\n"
  },
  {
    "path": "ocean_lib/models/df/test/conftest.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\nfrom conftest_ganache import *\n\n\n@pytest.fixture\ndef ocean(publisher_ocean):\n    return publisher_ocean\n"
  },
  {
    "path": "ocean_lib/models/df/test/test_df_rewards.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\n\n@pytest.mark.unit\ndef test1(ocean):\n    # df-py/util/test has thorough tests, so keep it super-simple here\n    assert ocean.df_rewards.address is not None\n"
  },
  {
    "path": "ocean_lib/models/df/test/test_df_strategy_v1.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\n\n@pytest.mark.unit\ndef test1(ocean):\n    # df-py/util/test has thorough tests, so keep it super-simple here\n    assert ocean.df_rewards.address is not None\n"
  },
  {
    "path": "ocean_lib/models/dispenser.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom typing import Optional, Union\n\nfrom enforce_typing import enforce_types\n\nfrom ocean_lib.ocean.util import from_wei, get_address_of_type\nfrom ocean_lib.web3_internal.constants import MAX_UINT256, ZERO_ADDRESS\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\n\"\"\"\ndef activate(dt_addr: str, max_tokens: int, max_balance: int) -> None:\n    activate dispenser after deactivation\n    :param dt_addr: datatoken address of ERC20\n    :param max_tokens: maximum amount of tokens\n    :param max_balance: maximum token balance\n    :return: None\n\ndef balance() -> int:\n    get dispenser balance\n    :return: balance in int\n\ndef deactivate(dt_addr: str) -> None:\n    deactivate dispenser\n    :param dt_addr: datatoken address of ERC20\n    :return: None\n\ndef dispense(dt_addr: str, amount: int, destination: str) -> None:\n    dispense an amount of tokens to a given destination address,\n    requires tx_dict with a sender that can dispense\n    :param dt_addr: address of the ERC20 token\n    :param amount: amount to dispense\n    :param destination: address of the account to receive dispensed tokens\n    :return: None\n\ndef getId() -> int:\n    get dispenser id\n    :return: dispenser id\n\ndef ownerWithdraw(dt_addr: str) -> None:\n    withdraw datatokens from dispenser,\n    requires tx_dict with a sender that can dispense\n    :param dt_addr: address of the ERC20 token. If missing, will withdraw all.\n    :return: None\n\ndef setAllowedSwapper(dt_addr: str, new_swapper_addr: str) -> None:\n    set allowed swapper to a new address\n    :param dt_addr: address of the ERC20 token\n    :param new_swapper_addr: address of the account to be set as swapper\n    :return: None\n\n\nThe following functions are wrapped with ocean.py helpers, but you can use the raw form if needed:\nstatus -> you can use the datatoken.dispenser_status() function as a better shorthand\ncreate -> you can use the datatoken.create_dispenser() function as a better shorthand\ndatatokensList -> a list of datatokens served by this dispenser, but we recommend retrieving each dispenser from its datatoken object\n\"\"\"\n\n\nclass Dispenser(ContractBase):\n    CONTRACT_NAME = \"Dispenser\"\n\n\nclass DispenserArguments:\n    def __init__(\n        self,\n        max_tokens: Optional[Union[int, str]] = MAX_UINT256,\n        max_balance: Optional[Union[int, str]] = MAX_UINT256,\n        with_mint: Optional[bool] = True,\n        allowed_swapper: Optional[str] = ZERO_ADDRESS,\n    ):\n        self.max_tokens = max_tokens\n        self.max_balance = max_balance\n        self.with_mint = with_mint\n        self.allowed_swapper = ContractBase.to_checksum_address(allowed_swapper)\n\n    def to_tuple(self, config_dict):\n        dispenser_address = get_address_of_type(config_dict, \"Dispenser\")\n        return (\n            ContractBase.to_checksum_address(dispenser_address),\n            self.max_tokens,\n            self.max_balance,\n            bool(self.with_mint),\n            self.allowed_swapper,\n        )\n\n\nclass DispenserStatus:\n    \"\"\"Status of dispenser smart contract, for a given datatoken\"\"\"\n\n    def __init__(self, status_tup):\n        \"\"\"\n        :param:status_tup -- returned from Dispenser.sol::status(dt_addr)\n        which is (bool active, address owner, bool isMinter,\n        uint256 maxTokens, uint256 maxBalance, uint256 balance,\n        address allowedSwapper)\n        \"\"\"\n        t = status_tup\n        self.active: bool = t[0]\n        self.owner_address: str = t[1]\n        self.is_minter: bool = t[2]\n        self.max_tokens: int = t[3]\n        self.max_balance: int = t[4]\n        self.balance: int = t[5]\n        self.allowed_swapper: int = t[6]\n\n    def __str__(self):\n        s = (\n            f\"DispenserStatus: \"\n            f\"  active = {self.active}\\n\"\n            f\"  owner_address = {self.owner_address}\\n\"\n            f\"  balance (of tokens) = {_strWithWei(self.balance)}\\n\"\n            f\"  is_minter (can mint more tokens?) = {self.is_minter}\\n\"\n            f\"  max_tokens (to dispense) = {_strWithWei(self.max_tokens)}\\n\"\n            f\"  max_balance (of requester) = {_strWithWei(self.max_balance)}\\n\"\n        )\n        if self.allowed_swapper.lower() == ZERO_ADDRESS.lower():\n            s += \"  allowed_swapper = anyone can request\\n\"\n        else:\n            s += f\"  allowed_swapper = {self.allowed_swapper}\\n\"\n        return s\n\n\n@enforce_types\ndef _strWithWei(x_wei: int) -> str:\n    return f\"{from_wei(x_wei)} ({x_wei} wei)\"\n"
  },
  {
    "path": "ocean_lib/models/erc721_token_factory_base.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom abc import ABC\n\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\n\nclass ERC721TokenFactoryBase(ABC, ContractBase):\n    pass\n"
  },
  {
    "path": "ocean_lib/models/factory_router.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\n\nclass FactoryRouter(ContractBase):\n    CONTRACT_NAME = \"FactoryRouter\"\n"
  },
  {
    "path": "ocean_lib/models/fixed_rate_exchange.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom typing import Optional, Union\n\nfrom enforce_typing import enforce_types\n\nfrom ocean_lib.models.factory_router import FactoryRouter\nfrom ocean_lib.ocean.util import get_address_of_type, get_from_address, str_with_wei\nfrom ocean_lib.web3_internal.constants import MAX_UINT256, ZERO_ADDRESS\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\nchecksum_addr = ContractBase.to_checksum_address\n\n\n@enforce_types\nclass ExchangeArguments:\n    def __init__(\n        self,\n        rate: Union[int, str],\n        base_token_addr: str,\n        owner_addr: Optional[str] = None,\n        publish_market_fee_collector: Optional[str] = None,\n        publish_market_fee: Union[int, str] = 0,\n        allowed_swapper: str = ZERO_ADDRESS,\n        full_info: bool = False,\n        dt_decimals: Optional[int] = None,\n    ):\n        self.rate = rate\n        self.base_token_addr = base_token_addr\n        self.owner_addr = owner_addr\n        self.publish_market_fee_collector = publish_market_fee_collector\n        self.allowed_swapper = checksum_addr(allowed_swapper)\n        self.rate = rate\n        self.publish_market_fee = publish_market_fee\n        self.dt_decimals = dt_decimals\n\n    def to_tuple(self, config_dict, tx_dict, dt_decimals=None):\n        FRE_addr = get_address_of_type(config_dict, \"FixedPrice\")\n\n        if not self.owner_addr:\n            self.owner_addr = get_from_address(tx_dict)\n\n        if not self.publish_market_fee_collector:\n            self.publish_market_fee_collector = get_from_address(tx_dict)\n\n        if not self.dt_decimals:\n            if not dt_decimals:\n                raise Exception(\n                    \"Must configure dt decimals either on arg creation or usage.\"\n                )\n\n            self.dt_decimals = dt_decimals\n\n        # TODO: move to top now?\n        from ocean_lib.models.datatoken_base import DatatokenBase  # isort:skip\n\n        self.BT = DatatokenBase.get_typed(config_dict, self.base_token_addr)\n\n        return (\n            FRE_addr,\n            [\n                checksum_addr(self.BT.address),\n                checksum_addr(self.owner_addr),\n                self.publish_market_fee_collector,\n                self.allowed_swapper,\n            ],\n            [\n                self.BT.decimals(),\n                self.dt_decimals,\n                self.rate,\n                self.publish_market_fee,\n                1,  # with mint\n            ],\n        )\n\n\n\"\"\"\nAttributes:\nMAX_FEE\nMIN_FEE\nMIN_RATE\n\nFunctions:\n\ndef balance() -> int:\n    returns FRE balance\n    :return: balance\n\ndef getExchanges() -> tuple:\n    get list of exchange contracts addresses\n    :return: tuple of contract addresses for each exchange\n\ndef generateExchangeId(bt_addr: str, dt_addr) -> str:\n    retrieve exchange id based on a pair of basetoken-datatoken address pair\n    :param bt_addr: address of base token\n    :param dt_addr: address of datatoken\n    :return: exchange id\n\ndef getId() -> int:\n    get exchange id\n    :return: id\n\ndef getNumberOfExchanges() -> int:\n    get number of exchange contracts\n    :return: number of exchanges\n\n\nThe following functions are wrapped with ocean.py helpers, especially in the OneExchange class, but you can use the raw form if needed.\nbuyDT\ncalcBaseInGivenOutDT\ncalcBaseOutGivenInDT\ncollectBT\ncollectDT\ncollectMarketFee\ncollectOceanFee\ncreateWithDecimals -> raw creation of the exchange contract, much easier to use from datatoken.create_exchange()\ngetAllowedSwapper\ngetBTSupply\ngetDTSupply\ngetExchange\ngetFeesInfo\ngetMarketFee\ngetOPCFee\ngetRate\nisActive\nsellDT\nsetAllowedSwapper\nsetRate\ntoggleExchangeState\ntoggleMintState\nupdateMarketFee\nupdateMarketFeeCollector\n\"\"\"\n\n\n@enforce_types\nclass ExchangeDetails:\n    def __init__(self, details_tup):\n        \"\"\"\n        :param:details_tup\n          -- returned from FixedRateExchange.sol::getExchange(exchange_id)\n        which is (exchangeOwner, datatoken, .., withMint)\n        \"\"\"\n        t = details_tup\n        self.owner: str = t[0]\n        self.datatoken: str = t[1]\n        self.dt_decimals: int = t[2]\n        self.base_token: str = t[3]\n        self.bt_decimals: int = t[4]\n        self.fixed_rate: int = t[5]\n        self.active: bool = t[6]\n        self.dt_supply: int = t[7]\n        self.bt_supply: int = t[8]\n        self.dt_balance: int = t[9]\n        self.bt_balance: int = t[10]\n        self.with_mint: bool = t[11]\n\n    def __str__(self):\n        s = (\n            f\"ExchangeDetails: \\n\"\n            f\"  datatoken = {self.datatoken}\\n\"\n            f\"  base_token = {self.base_token}\\n\"\n            f\"  fixed_rate (price) = {str_with_wei(self.fixed_rate)}\\n\"\n            f\"  active = {self.active}\\n\"\n            f\"  dt_supply = {str_with_wei(self.dt_supply)}\\n\"\n            f\"  bt_supply = {str_with_wei(self.bt_supply)}\\n\"\n            f\"  dt_balance = {str_with_wei(self.dt_balance)}\\n\"\n            f\"  bt_balance = {str_with_wei(self.bt_balance)}\\n\"\n            f\"  with_mint = {self.with_mint}\\n\"\n            f\"  dt_decimals = {self.dt_decimals}\\n\"\n            f\"  bt_decimals = {self.bt_decimals}\\n\"\n            f\"  owner = {self.owner}\\n\"\n        )\n        return s\n\n\n@enforce_types\nclass ExchangeFeeInfo:\n    def __init__(self, fees_tup):\n        \"\"\"\n        :param:details_tup\n          -- returned from FixedRateExchange.sol::getFeesInfo(exchange_id)\n        which is {(publish)market(swap)Fee, ..., oceanFeeAvailable}\n        \"\"\"\n        t = fees_tup\n        self.publish_market_fee: int = t[0]\n        self.publish_market_fee_collector: str = t[1]  # address\n        self.opc_fee: int = t[2]\n        self.publish_market_fee_available = t[3]  # in base tokens\n        self.ocean_fee_available = t[4]  # in base tokens. Goes to OPC\n\n    def __str__(self):\n        s = (\n            f\"ExchangeFeeInfo: \\n\"\n            f\"  publish_market_fee = {str_with_wei(self.publish_market_fee)}\\n\"\n            f\"  publish_market_fee_available\"\n            f\" = {str_with_wei(self.publish_market_fee_available)}\\n\"\n            f\"  publish_market_fee_collector\"\n            f\" = {self.publish_market_fee_collector}\\n\"\n            f\"  opc_fee\"\n            f\" = {str_with_wei(self.opc_fee)}\\n\"\n            f\"  ocean_fee_available (to opc)\"\n            f\" = {str_with_wei(self.ocean_fee_available)}\\n\"\n        )\n        return s\n\n\n@enforce_types\nclass BtNeeded:\n    def __init__(self, tup):\n        self.base_token_amount = tup[0]\n        self.ocean_fee_amount = tup[1]\n        self.publish_market_fee_amount = tup[2]\n        self.consume_market_fee_amount = tup[3]\n\n\n@enforce_types\nclass BtReceived:\n    def __init__(self, tup):\n        self.base_token_amount = tup[0]\n        self.ocean_fee_amount = tup[1]\n        self.publish_market_fee_amount = tup[2]\n        self.consume_market_fee_amount = tup[3]\n\n\n@enforce_types\nclass FixedRateExchange(ContractBase):\n    CONTRACT_NAME = \"FixedRateExchange\"\n\n    def get_opc_collector(self) -> str:\n        \"\"\"Returns address that collects fees for Ocean Protocol Community\"\"\"\n        router_addr = self.router()\n        router = FactoryRouter(self.config_dict, router_addr)\n        return router.getOPCCollector()\n\n\n@enforce_types\nclass OneExchange:\n    \"\"\"\n    Clean object-oriented class for a sole exchange, between two tokens.\n\n    It's a bit like FixedRateExchange, but for just one exchange_id.\n      Therefore its methods don't need the exchange_id argument.\n\n    While it doesn't have a corresponding smart contract. It can be viewed as\n      a slice of the FixedRateExchange contract.\n    \"\"\"\n\n    @enforce_types\n    def __init__(self, FRE: FixedRateExchange, exchange_id):\n        self._FRE = FRE\n        self._id = exchange_id\n\n    @property\n    def FRE(self):\n        return self._FRE\n\n    @property\n    def exchange_id(self):\n        return self._id\n\n    @property\n    def address(self):\n        return self._FRE.address\n\n    # From here on, the methods have a 1:1 mapping to FixedRateExchange.sol.\n    # In some cases, there's a rename for better clarity\n    # It's easy to tell the original method name: see what this class calls.\n\n    @enforce_types\n    def BT_needed(\n        self,\n        DT_amt: Union[int, str],\n        consume_market_fee: Union[int, str],\n        full_info: bool = False,\n    ) -> Union[int, BtNeeded]:\n        \"\"\"\n        Returns an int - how many BTs you need, to buy target amt of DTs.\n        Or, for an object with all details, set full_info=True.\n        \"\"\"\n        tup = self._FRE.calcBaseInGivenOutDT(self._id, DT_amt, consume_market_fee)\n        bt_needed_obj = BtNeeded(tup)\n        if full_info:\n            return bt_needed_obj\n        return bt_needed_obj.base_token_amount\n\n    @enforce_types\n    def BT_received(\n        self,\n        DT_amt: Union[int, str],\n        consume_market_fee: Union[int, str],\n        full_info: bool = False,\n    ) -> Union[int, BtReceived]:\n        \"\"\"\n        Returns an int - how many BTs you receive, in selling given amt of DTs.\n        Or, for an object with all details, set full_info=True.\n        \"\"\"\n        tup = self._FRE.calcBaseOutGivenInDT(self._id, DT_amt, consume_market_fee)\n        bt_recd_obj = BtReceived(tup)\n        if full_info:\n            return bt_recd_obj\n        return bt_recd_obj.base_token_amount\n\n    @enforce_types\n    def buy_DT(\n        self,\n        datatoken_amt: Union[int, str],\n        tx_dict: dict,\n        max_basetoken_amt=MAX_UINT256,\n        consume_market_fee_addr: Optional[str] = ZERO_ADDRESS,\n        consume_market_fee: Optional[Union[int, str]] = 0,\n    ):\n        \"\"\"\n        Buy datatokens via fixed-rate exchange.\n\n        This wraps the smart contract method FixedRateExchange.buyDT()\n          with a simpler interface.\n\n        Params:\n        - datatoken_amt - how many DT to buy? In wei, or str\n        - max_basetoken_amt - maximum to spend\n        - consume_market_fee_addr - market facilitating this swap\n        - consume_market_fee - fee charged by market that's facilitating\n        - tx_dict - e.g. {\"from\": alice_wallet}\n        \"\"\"\n        # import now, to avoid circular import\n        # TODO: maybe we can move it now?\n        from ocean_lib.models.datatoken_base import DatatokenBase\n\n        details = self.details\n        BT = DatatokenBase.get_typed(self._FRE.config_dict, details.base_token)\n        buyer_addr = get_from_address(tx_dict)\n\n        BT_needed = self.BT_needed(datatoken_amt, consume_market_fee)\n        assert BT.balanceOf(buyer_addr) >= BT_needed, \"not enough funds\"\n\n        tx = self._FRE.buyDT(\n            self._id,\n            datatoken_amt,\n            max_basetoken_amt,\n            consume_market_fee_addr,\n            consume_market_fee,\n            tx_dict,\n        )\n        return tx\n\n    @enforce_types\n    def sell_DT(\n        self,\n        datatoken_amt: Union[int, str],\n        tx_dict: dict,\n        min_basetoken_amt: Union[int, str] = 0,\n        consume_market_fee_addr: str = ZERO_ADDRESS,\n        consume_market_fee: Union[int, str] = 0,\n    ):\n        \"\"\"\n        Sell datatokens to the exchange, in return for e.g. OCEAN\n\n        This wraps the smart contract method FixedRateExchange.sellDT()\n          with a simpler interface.\n\n        Main params:\n        - datatoken_amt - how many DT to sell? In wei, or str\n        - min_basetoken_amt - min basetoken to get back\n        - consume_market_fee_addr - market facilitating this swap\n        - consume_market_fee - fee charged by market that's facilitating\n        - tx_dict - e.g. {\"from\": alice_wallet}\n        \"\"\"\n        tx = self._FRE.sellDT(\n            self._id,\n            datatoken_amt,\n            min_basetoken_amt,\n            consume_market_fee_addr,\n            consume_market_fee,\n            tx_dict,\n        )\n        return tx\n\n    @enforce_types\n    def collect_BT(self, amount: Union[int, str], tx_dict: dict):\n        \"\"\"\n        This exchange collects fees denominated in base tokens, and\n          records updates into its `bt_balance`.\n\n        *This method* triggers the exchange to send `amount` fees\n          to the datatoken's payment collector (ERC20.getPaymentCollector)\n\n        'amount' must be <= this exchange's bt_balance, of course.\n\n        Anyone can call this method, since the receiver is constant.\n        \"\"\"\n        return self._FRE.collectBT(self._id, amount, tx_dict)\n\n    @enforce_types\n    def collect_DT(self, amount: Union[int, str], tx_dict: dict):\n        \"\"\"\n        This exchange collects fees denominated in datatokens, and\n          records updates into its `dt_balance`.\n\n        *This method* triggers the exchange to send `amount` fees\n          to the datatoken's payment collector (ERC20.getPaymentCollector)\n\n        'amount' must be <= this exchange's dt_balance, of course.\n\n        Anyone can call this method, since the receiver is constant.\n        \"\"\"\n        return self._FRE.collectDT(self._id, amount, tx_dict)\n\n    @enforce_types\n    def collect_publish_market_fee(self, tx_dict: dict):\n        \"\"\"\n        This exchange collects fees for the publishing market, and\n          records updates into its `market_fee_available`.\n\n        *This method* triggers the exchange to send all available market fees\n          to this exchange's market fee collector (`market_fee_collector`).\n\n        Anyone can call this method, since the receiver is constant.\n        \"\"\"\n        return self._FRE.collectMarketFee(self._id, tx_dict)\n\n    @enforce_types\n    def collect_opc_fee(self, tx_dict: dict):\n        \"\"\"\n        This exchange collects fees for the Ocean Protocol Community (OPC), and\n          records updates into its `ocean_fee_available`.\n\n        *This method* triggers the exchange to send all available OPC fees\n          to the OPC Collector (router.getOPCCollector).\n\n        Anyone can call this method, since the receiver is constant.\n        \"\"\"\n        return self._FRE.collectOceanFee(self._id, tx_dict)\n\n    @enforce_types\n    def update_publish_market_fee_collector(self, new_addr: str, tx_dict):\n        \"\"\"Update which address collects the publish market swap fees\"\"\"\n        return self._FRE.updateMarketFeeCollector(self._id, new_addr, tx_dict)\n\n    @enforce_types\n    def update_publish_market_fee(self, new_amt: Union[str, int], tx_dict):\n        \"\"\"Update the value of the publish market swap fee\"\"\"\n        return self._FRE.updateMarketFee(self._id, new_amt, tx_dict)\n\n    @enforce_types\n    def get_publish_market_fee(self) -> int:\n        \"\"\"Return the value of the publish market swap fee\"\"\"\n        return self._FRE.getMarketFee(self._id)\n\n    @enforce_types\n    def set_rate(self, new_rate: Union[int, str], tx_dict: dict):\n        \"\"\"Set price = # base tokens needed to buy 1 datatoken\"\"\"\n        return self._FRE.setRate(self._id, new_rate, tx_dict)\n\n    @enforce_types\n    def toggle_active(self, tx_dict: dict):\n        \"\"\"Switch whether 'active' from True <> False\"\"\"\n        return self._FRE.toggleExchangeState(self._id, tx_dict)\n\n    @enforce_types\n    def set_allowed_swapper(self, new_addr: str, tx_dict: dict):\n        \"\"\"Set allowed swapper. ZERO_ADDRESS means anyone can swap.\"\"\"\n        return self._FRE.setAllowedSwapper(self._id, new_addr, tx_dict)\n\n    @enforce_types\n    def get_rate(self) -> int:\n        \"\"\"Get price = # base tokens needed to buy 1 datatoken\"\"\"\n        return self._FRE.getRate(self._id)\n\n    @enforce_types\n    def get_dt_supply(self) -> int:\n        \"\"\"Return the current supply of datatokens in this exchange\"\"\"\n        return self._FRE.getDTSupply(self._id)\n\n    @enforce_types\n    def get_bt_supply(self) -> int:\n        \"\"\"Return the current supply of base tokens in this exchange\"\"\"\n        return self._FRE.getBTSupply(self._id)\n\n    @property\n    def details(self) -> ExchangeDetails:\n        \"\"\"Get all the exchange's details, as an object\"\"\"\n        tup = self._FRE.getExchange(self._id)\n        return ExchangeDetails(tup)\n\n    @enforce_types\n    def get_allowed_swapper(self) -> str:\n        \"\"\"Get allowed swapper. ZERO_ADDRESS means anyone can swap.\"\"\"\n        return self._FRE.getAllowedSwapper(self._id)\n\n    @property\n    def exchange_fees_info(self) -> ExchangeFeeInfo:\n        \"\"\"Get fee information for this exchange, as an object\"\"\"\n        tup = self._FRE.getFeesInfo(self._id)\n        return ExchangeFeeInfo(tup)\n\n    @enforce_types\n    def is_active(self) -> bool:\n        \"\"\"Get whether exchange is 'active'\"\"\"\n        return self._FRE.isActive(self._id)\n"
  },
  {
    "path": "ocean_lib/models/test/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "ocean_lib/models/test/conftest.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom conftest_ganache import *\n"
  },
  {
    "path": "ocean_lib/models/test/test_data_nft.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport json\nfrom base64 import b64decode\n\nimport pytest\nfrom web3 import Web3\nfrom web3.logs import DISCARD\n\nfrom ocean_lib.models.data_nft import DataNFTPermissions\nfrom ocean_lib.models.data_nft_factory import DataNFTFactoryContract\nfrom ocean_lib.models.datatoken_base import (\n    DatatokenArguments,\n    DatatokenBase,\n    TokenFeeInfo,\n)\nfrom ocean_lib.ocean.util import get_address_of_type, to_wei\n\nBLOB = \"f8929916089218bdb4aa78c3ecd16633afd44b8aef89299160\"\n\n\n@pytest.mark.unit\ndef test_permissions(\n    publisher_wallet,\n    consumer_wallet,\n    another_consumer_wallet,\n    config,\n    data_nft,\n):\n    \"\"\"Tests permissions' functions.\"\"\"\n    assert data_nft.name() == \"NFT\"\n    assert data_nft.symbol() == \"NFTSYMBOL\"\n    assert data_nft.balanceOf(publisher_wallet.address) == 1\n    # Tests if the NFT was initialized\n    assert data_nft.isInitialized()\n\n    # Tests adding a manager successfully\n    data_nft.addManager(consumer_wallet.address, {\"from\": publisher_wallet})\n    assert data_nft.getPermissions(consumer_wallet.address)[DataNFTPermissions.MANAGER]\n\n    token_uri = data_nft.tokenURI(1).replace(\"data:application/json;base64,\", \"\")\n    decoded_token_uri = json.loads(b64decode(token_uri))\n\n    assert decoded_token_uri[\"name\"] == \"NFT\"\n    assert decoded_token_uri[\"symbol\"] == \"NFTSYMBOL\"\n    assert decoded_token_uri[\"background_color\"] == \"141414\"\n    assert decoded_token_uri[\"image_data\"].startswith(\"data:image/svg+xm\")\n\n    # Tests failing clearing permissions\n    with pytest.raises(Exception, match=\"not NFTOwner\"):\n        data_nft.cleanPermissions({\"from\": another_consumer_wallet})\n\n    # Tests clearing permissions\n    data_nft.addToCreateERC20List(publisher_wallet.address, {\"from\": publisher_wallet})\n    data_nft.addToCreateERC20List(\n        another_consumer_wallet.address, {\"from\": publisher_wallet}\n    )\n    assert data_nft.getPermissions(publisher_wallet.address)[\n        DataNFTPermissions.DEPLOY_DATATOKEN\n    ]\n    assert data_nft.getPermissions(another_consumer_wallet.address)[\n        DataNFTPermissions.DEPLOY_DATATOKEN\n    ]\n    # Still is not the NFT owner, cannot clear permissions then\n    with pytest.raises(Exception, match=\"not NFTOwner\"):\n        data_nft.cleanPermissions({\"from\": another_consumer_wallet})\n\n    data_nft.cleanPermissions({\"from\": publisher_wallet})\n\n    assert not (\n        data_nft.getPermissions(publisher_wallet.address)[\n            DataNFTPermissions.DEPLOY_DATATOKEN\n        ]\n    )\n    assert not (\n        data_nft.getPermissions(consumer_wallet.address)[DataNFTPermissions.MANAGER]\n    )\n    assert not (\n        data_nft.getPermissions(another_consumer_wallet.address)[\n            DataNFTPermissions.DEPLOY_DATATOKEN\n        ]\n    )\n\n    # Tests failing adding a new manager by another user different from the NFT owner\n    data_nft.addManager(publisher_wallet.address, {\"from\": publisher_wallet})\n    assert data_nft.getPermissions(publisher_wallet.address)[DataNFTPermissions.MANAGER]\n    assert not (\n        data_nft.getPermissions(consumer_wallet.address)[DataNFTPermissions.MANAGER]\n    )\n    with pytest.raises(Exception, match=\"not NFTOwner\"):\n        data_nft.addManager(another_consumer_wallet.address, {\"from\": consumer_wallet})\n    assert not (\n        data_nft.getPermissions(another_consumer_wallet.address)[\n            DataNFTPermissions.MANAGER\n        ]\n    )\n\n    # Tests removing manager\n    data_nft.addManager(consumer_wallet.address, {\"from\": publisher_wallet})\n    assert data_nft.getPermissions(consumer_wallet.address)[DataNFTPermissions.MANAGER]\n    data_nft.removeManager(consumer_wallet.address, {\"from\": publisher_wallet})\n    assert not (\n        data_nft.getPermissions(consumer_wallet.address)[DataNFTPermissions.MANAGER]\n    )\n\n    # Tests failing removing a manager if it has not the NFT owner role\n    data_nft.addManager(consumer_wallet.address, {\"from\": publisher_wallet})\n    assert data_nft.getPermissions(consumer_wallet.address)[DataNFTPermissions.MANAGER]\n    with pytest.raises(Exception, match=\"not NFTOwner\"):\n        data_nft.removeManager(publisher_wallet.address, {\"from\": consumer_wallet})\n    assert data_nft.getPermissions(publisher_wallet.address)[DataNFTPermissions.MANAGER]\n\n    # Tests removing the NFT owner from the manager role\n    data_nft.removeManager(publisher_wallet.address, {\"from\": publisher_wallet})\n    assert not (\n        data_nft.getPermissions(publisher_wallet.address)[DataNFTPermissions.MANAGER]\n    )\n    data_nft.addManager(publisher_wallet.address, {\"from\": publisher_wallet})\n    assert data_nft.getPermissions(publisher_wallet.address)[DataNFTPermissions.MANAGER]\n\n    # Tests failing calling execute_call function if the user is not manager\n    assert not (\n        data_nft.getPermissions(another_consumer_wallet.address)[\n            DataNFTPermissions.MANAGER\n        ]\n    )\n    with pytest.raises(Exception, match=\"NOT MANAGER\"):\n        data_nft.executeCall(\n            0,\n            consumer_wallet.address,\n            10,\n            Web3.to_hex(text=\"SomeData\"),\n            {\"from\": another_consumer_wallet},\n        )\n\n    # Tests calling execute_call with a manager role\n    assert data_nft.getPermissions(publisher_wallet.address)[DataNFTPermissions.MANAGER]\n    tx = data_nft.executeCall(\n        0,\n        consumer_wallet.address,\n        10,\n        Web3.to_hex(text=\"SomeData\"),\n        {\"from\": consumer_wallet},\n    )\n    assert tx, \"Could not execute call to consumer.\"\n\n    # Tests setting new data\n    data_nft.addTo725StoreList(consumer_wallet.address, {\"from\": publisher_wallet})\n    assert data_nft.getPermissions(consumer_wallet.address)[DataNFTPermissions.STORE]\n    data_nft.setNewData(\n        b\"ARBITRARY_KEY\",\n        b\"SomeData\",\n        {\"from\": consumer_wallet},\n    )\n    assert data_nft.getData(b\"ARBITRARY_KEY\").hex() == b\"SomeData\".hex()\n\n    # Tests failing setting new data if user has not STORE UPDATER role.\n    assert not (\n        data_nft.getPermissions(another_consumer_wallet.address)[\n            DataNFTPermissions.STORE\n        ]\n    )\n    with pytest.raises(Exception, match=\"NOT STORE UPDATER\"):\n        data_nft.setNewData(\n            b\"ARBITRARY_KEY\",\n            b\"SomeData\",\n            {\"from\": another_consumer_wallet},\n        )\n\n    # Tests failing setting ERC20 data\n    with pytest.raises(Exception, match=\"NOT ERC20 Contract\"):\n        data_nft.setDataERC20(\n            b\"FOO_KEY\",\n            b\"SomeData\",\n            {\"from\": consumer_wallet},\n        )\n    assert data_nft.getData(b\"FOO_KEY\").hex() == b\"\".hex()\n\n\ndef test_add_and_remove_permissions(\n    publisher_wallet, consumer_wallet, config, data_nft\n):\n    # Assert consumer has no permissions\n    permissions = data_nft.getPermissions(consumer_wallet.address)\n    assert not permissions[DataNFTPermissions.MANAGER]\n    assert not permissions[DataNFTPermissions.DEPLOY_DATATOKEN]\n    assert not permissions[DataNFTPermissions.UPDATE_METADATA]\n    assert not permissions[DataNFTPermissions.STORE]\n\n    # Grant consumer all permissions, one by one\n    data_nft.addManager(consumer_wallet.address, {\"from\": publisher_wallet})\n    data_nft.addToCreateERC20List(consumer_wallet.address, {\"from\": publisher_wallet})\n    data_nft.addToMetadataList(consumer_wallet.address, {\"from\": publisher_wallet})\n    data_nft.addTo725StoreList(consumer_wallet.address, {\"from\": publisher_wallet})\n\n    # Assert consumer has all permissions\n    permissions = data_nft.getPermissions(consumer_wallet.address)\n    assert permissions[DataNFTPermissions.MANAGER]\n    assert permissions[DataNFTPermissions.DEPLOY_DATATOKEN]\n    assert permissions[DataNFTPermissions.UPDATE_METADATA]\n    assert permissions[DataNFTPermissions.STORE]\n\n    # Revoke all consumer permissions, one by one\n    data_nft.removeManager(consumer_wallet.address, {\"from\": publisher_wallet})\n    data_nft.removeFromCreateERC20List(\n        consumer_wallet.address, {\"from\": publisher_wallet}\n    )\n    data_nft.removeFromMetadataList(consumer_wallet.address, {\"from\": publisher_wallet})\n    data_nft.removeFrom725StoreList(consumer_wallet.address, {\"from\": publisher_wallet})\n\n    # Assert consumer has no permissions\n    permissions = data_nft.getPermissions(consumer_wallet.address)\n    assert not permissions[DataNFTPermissions.MANAGER]\n    assert not permissions[DataNFTPermissions.DEPLOY_DATATOKEN]\n    assert not permissions[DataNFTPermissions.UPDATE_METADATA]\n    assert not permissions[DataNFTPermissions.STORE]\n\n\n@pytest.mark.unit\ndef test_success_update_metadata(publisher_wallet, consumer_wallet, config, data_nft):\n    \"\"\"Tests updating the metadata flow.\"\"\"\n    assert not (\n        data_nft.getPermissions(consumer_wallet.address)[\n            DataNFTPermissions.UPDATE_METADATA\n        ]\n    )\n    data_nft.addToMetadataList(consumer_wallet.address, {\"from\": publisher_wallet})\n    metadata_info = data_nft.getMetaData()\n    assert not metadata_info[3]\n\n    receipt = data_nft.setMetaData(\n        1,\n        \"http://myprovider:8030\",\n        b\"0x123\",\n        Web3.to_bytes(hexstr=BLOB),\n        Web3.to_bytes(hexstr=BLOB),\n        Web3.to_bytes(hexstr=BLOB),\n        [],\n        {\"from\": consumer_wallet},\n    )\n\n    event = data_nft.contract.events.MetadataCreated().process_receipt(\n        receipt, errors=DISCARD\n    )[0]\n    assert event.args.decryptorUrl == \"http://myprovider:8030\"\n\n    metadata_info = data_nft.getMetaData()\n    assert metadata_info[3]\n    assert metadata_info[0] == \"http://myprovider:8030\"\n\n    receipt = data_nft.setMetaData(\n        1,\n        \"http://foourl\",\n        b\"0x123\",\n        Web3.to_bytes(hexstr=BLOB),\n        Web3.to_bytes(hexstr=BLOB),\n        Web3.to_bytes(hexstr=BLOB),\n        [],\n        {\"from\": consumer_wallet},\n    )\n\n    event = data_nft.contract.events.MetadataUpdated().process_receipt(\n        receipt, errors=DISCARD\n    )[0]\n    assert event.args.decryptorUrl == \"http://foourl\"\n\n    metadata_info = data_nft.getMetaData()\n    assert metadata_info[3]\n    assert metadata_info[0] == \"http://foourl\"\n\n    # Update tokenURI and set metadata in one call\n    receipt = data_nft.setMetaDataAndTokenURI(\n        (\n            1,\n            \"http://foourl\",\n            b\"0x123\",\n            Web3.to_bytes(hexstr=BLOB),\n            Web3.to_bytes(hexstr=BLOB),\n            Web3.to_bytes(hexstr=BLOB),\n            1,\n            \"https://anothernewurl.com/nft/\",\n            [],\n        ),\n        {\"from\": publisher_wallet},\n    )\n\n    event = data_nft.contract.events.TokenURIUpdate().process_receipt(\n        receipt, errors=DISCARD\n    )[0]\n    assert event.args.tokenURI == \"https://anothernewurl.com/nft/\"\n    assert event.args.updatedBy == publisher_wallet.address\n\n    event = data_nft.contract.events.MetadataUpdated().process_receipt(\n        receipt, errors=DISCARD\n    )[0]\n    assert event.args.decryptorUrl == \"http://foourl\"\n\n    metadata_info = data_nft.getMetaData()\n    assert metadata_info[3]\n    assert metadata_info[0] == \"http://foourl\"\n\n    # Consumer self-revokes permission to update metadata\n    data_nft.removeFromMetadataList(consumer_wallet.address, {\"from\": consumer_wallet})\n    assert not data_nft.getPermissions(consumer_wallet.address)[\n        DataNFTPermissions.UPDATE_METADATA\n    ]\n\n\ndef test_fails_update_metadata(consumer_wallet, publisher_wallet, config, data_nft):\n    \"\"\"Tests failure of calling update metadata function when the role of the user is not METADATA UPDATER.\"\"\"\n    assert not (\n        data_nft.getPermissions(consumer_wallet.address)[\n            DataNFTPermissions.UPDATE_METADATA\n        ]\n    )\n\n    with pytest.raises(Exception, match=\"NOT METADATA_ROLE\"):\n        data_nft.setMetaData(\n            1,\n            \"http://myprovider:8030\",\n            b\"0x123\",\n            BLOB.encode(\"utf-8\"),\n            BLOB,\n            BLOB,\n            [],\n            {\"from\": consumer_wallet},\n        )\n\n\n@pytest.mark.unit\ndef test_create_datatoken(\n    publisher_wallet,\n    consumer_wallet,\n    config,\n    data_nft_factory: DataNFTFactoryContract,\n    data_nft,\n):\n    \"\"\"Tests calling create an ERC20 by the owner.\"\"\"\n    assert data_nft.getPermissions(publisher_wallet.address)[\n        DataNFTPermissions.DEPLOY_DATATOKEN\n    ]\n\n    datatoken = data_nft.create_datatoken(\n        {\"from\": publisher_wallet},\n        \"DT1\",\n        \"DT1Symbol\",\n        fee_manager=consumer_wallet.address,\n    )\n\n    assert datatoken, \"Could not create ERC20.\"\n\n    dt_ent = data_nft.create_datatoken(\n        {\"from\": publisher_wallet},\n        datatoken_args=DatatokenArguments(\n            template_index=2,\n            name=\"Datatoken2DT1\",\n            symbol=\"Datatoken2DT1Symbol\",\n            minter=publisher_wallet.address,\n            fee_manager=consumer_wallet.address,\n            bytess=[b\"\"],\n            cap=to_wei(0.1),\n        ),\n    )\n    assert dt_ent, \"Could not create datatoken template 2 with explicit parameters\"\n\n    dt_ent = data_nft.create_datatoken(\n        {\"from\": publisher_wallet},\n        name=\"Datatoken2DT1\",\n        symbol=\"Datatoken2DT1Symbol\",\n        cap=to_wei(0.1),\n    )\n    assert dt_ent, \"Could not create datatoken template 2 with implicit parameters.\"\n\n\ndef test_create_datatoken_with_usdc_order_fee(\n    config: dict, publisher_wallet, data_nft_factory: DataNFTFactoryContract, data_nft\n):\n    \"\"\"Create an ERC20 with order fees ( 5 USDC, going to publishMarketAddress)\"\"\"\n    usdc = DatatokenBase.get_typed(config, get_address_of_type(config, \"MockUSDC\"))\n    publish_market_order_fee_amount_in_wei = to_wei(5)\n    dt = data_nft.create_datatoken(\n        {\"from\": publisher_wallet},\n        DatatokenArguments(\n            name=\"DT1\",\n            symbol=\"DT1Symbol\",\n            publish_market_order_fees=TokenFeeInfo(\n                address=publisher_wallet.address,\n                token=usdc.address,\n                amount=publish_market_order_fee_amount_in_wei,\n            ),\n        ),\n    )\n\n    # Check publish fee info\n    publish_market_fees = dt.get_publish_market_order_fees()\n    assert publish_market_fees.address == publisher_wallet.address\n    assert publish_market_fees.token == usdc.address\n    assert publish_market_fees.amount == publish_market_order_fee_amount_in_wei\n\n\n@pytest.mark.unit\ndef test_create_datatoken_with_non_owner(\n    publisher_wallet,\n    consumer_wallet,\n    data_nft_factory: DataNFTFactoryContract,\n    config,\n    data_nft,\n):\n    \"\"\"Tests creating an ERC20 token by wallet other than nft owner\"\"\"\n    # Assert consumer cannot create ERC20\n    assert not data_nft.getPermissions(consumer_wallet.address)[\n        DataNFTPermissions.DEPLOY_DATATOKEN\n    ]\n\n    # Grant consumer permission to create ERC20\n    data_nft.addToCreateERC20List(consumer_wallet.address, {\"from\": publisher_wallet})\n    assert data_nft.getPermissions(consumer_wallet.address)[\n        DataNFTPermissions.DEPLOY_DATATOKEN\n    ]\n\n    # Consumer creates ERC20\n    dt = data_nft.create_datatoken(\n        {\"from\": consumer_wallet},\n        DatatokenArguments(\n            name=\"DT1\",\n            symbol=\"DT1Symbol\",\n            minter=publisher_wallet.address,\n            fee_manager=publisher_wallet.address,\n        ),\n    )\n    assert dt, \"Failed to create ERC20 token.\"\n\n    # Consumer self-revokes permission to create ERC20\n    data_nft.removeFromCreateERC20List(\n        consumer_wallet.address, {\"from\": consumer_wallet}\n    )\n    assert not data_nft.getPermissions(consumer_wallet.address)[\n        DataNFTPermissions.DEPLOY_DATATOKEN\n    ]\n\n\n@pytest.mark.unit\ndef test_fail_creating_erc20(\n    consumer_wallet,\n    publisher_wallet,\n    config,\n    data_nft,\n):\n    \"\"\"Tests failure for creating ERC20 token.\"\"\"\n    assert not (\n        data_nft.getPermissions(consumer_wallet.address)[\n            DataNFTPermissions.DEPLOY_DATATOKEN\n        ]\n    )\n    with pytest.raises(Exception, match=\"NOT ERC20DEPLOYER_ROLE\"):\n        data_nft.create_datatoken(\n            {\"from\": consumer_wallet},\n            name=\"DT1\",\n            symbol=\"DT1Symbol\",\n            minter=publisher_wallet.address,\n        )\n\n\n@pytest.mark.unit\ndef test_erc721_datatoken_functions(\n    publisher_wallet,\n    consumer_wallet,\n    config,\n    data_NFT_and_DT,\n):\n    \"\"\"Tests ERC721 Template functions for ERC20 tokens.\"\"\"\n    data_nft, datatoken = data_NFT_and_DT\n    assert len(data_nft.getTokensList()) == 1\n    assert data_nft.isDeployed(datatoken.address)\n\n    assert not data_nft.isDeployed(consumer_wallet.address)\n    receipt = data_nft.setTokenURI(\n        1,\n        \"https://newurl.com/nft/\",\n        {\"from\": publisher_wallet},\n    )\n    registered_event = data_nft.contract.events.TokenURIUpdate().process_receipt(\n        receipt, errors=DISCARD\n    )[0]\n\n    assert registered_event, \"Cannot find TokenURIUpdate event.\"\n    assert registered_event.args.updatedBy == publisher_wallet.address\n    assert registered_event.args.tokenID == 1\n    assert registered_event.args.blockNumber == receipt.blockNumber\n    assert data_nft.tokenURI(1) == \"https://newurl.com/nft/\"\n    assert data_nft.tokenURI(1) == registered_event.args.tokenURI\n\n    # Tests failing setting token URI by another user\n    with pytest.raises(Exception, match=\"not NFTOwner\"):\n        data_nft.setTokenURI(\n            1,\n            \"https://foourl.com/nft/\",\n            {\"from\": consumer_wallet},\n        )\n\n    # Tests transfer functions\n    datatoken.mint(\n        consumer_wallet.address,\n        to_wei(0.2),\n        {\"from\": publisher_wallet},\n    )\n    assert datatoken.balanceOf(consumer_wallet.address) == to_wei(0.2)\n    assert data_nft.ownerOf(1) == publisher_wallet.address\n\n    data_nft.transferFrom(\n        publisher_wallet.address,\n        consumer_wallet.address,\n        1,\n        {\"from\": publisher_wallet},\n    )\n    assert data_nft.balanceOf(publisher_wallet.address) == 0\n    assert data_nft.ownerOf(1) == consumer_wallet.address\n    assert data_nft.getPermissions(consumer_wallet.address)[\n        DataNFTPermissions.DEPLOY_DATATOKEN\n    ]\n    data_nft.create_datatoken(\n        {\"from\": consumer_wallet},\n        name=\"DT1\",\n        symbol=\"DT1Symbol\",\n        minter=publisher_wallet.address,\n    )\n    with pytest.raises(Exception, match=\"NOT MINTER\"):\n        datatoken.mint(\n            consumer_wallet.address,\n            to_wei(1),\n            {\"from\": consumer_wallet},\n        )\n\n    datatoken.addMinter(consumer_wallet.address, {\"from\": consumer_wallet})\n    datatoken.mint(\n        consumer_wallet.address,\n        to_wei(0.2),\n        {\"from\": consumer_wallet},\n    )\n    assert datatoken.balanceOf(consumer_wallet.address) == to_wei(0.4)\n\n\n@pytest.mark.unit\ndef test_fail_transfer_function(consumer_wallet, publisher_wallet, config, data_nft):\n    \"\"\"Tests failure of using the transfer functions.\"\"\"\n    with pytest.raises(\n        Exception,\n        match=\"transfer caller is not owner nor approved\",\n    ):\n        data_nft.transferFrom(\n            publisher_wallet.address,\n            consumer_wallet.address,\n            1,\n            {\"from\": consumer_wallet},\n        )\n\n    # Tests for safe transfer as well\n    with pytest.raises(\n        Exception,\n        match=\"transfer caller is not owner nor approved\",\n    ):\n        data_nft.safeTransferFrom(\n            publisher_wallet.address,\n            consumer_wallet.address,\n            1,\n            {\"from\": consumer_wallet},\n        )\n\n\ndef test_transfer_nft(\n    config,\n    publisher_wallet,\n    consumer_wallet,\n    factory_router,\n    data_nft_factory,\n    publisher_ocean,\n):\n    \"\"\"Tests transferring the NFT before deploying an ERC20, a pool, a FRE.\"\"\"\n\n    data_nft = data_nft_factory.create(\n        {\"from\": publisher_wallet},\n        \"NFT to TRANSFER\",\n        \"NFTtT\",\n        additional_datatoken_deployer=consumer_wallet.address,\n    )\n    assert data_nft.name() == \"NFT to TRANSFER\"\n    assert data_nft.symbol() == \"NFTtT\"\n\n    receipt = data_nft.safeTransferFrom(\n        publisher_wallet.address,\n        consumer_wallet.address,\n        1,\n        {\"from\": publisher_wallet},\n    )\n    transfer_event = data_nft.contract.events.Transfer().process_receipt(\n        receipt, errors=DISCARD\n    )[0]\n\n    assert getattr(transfer_event.args, \"from\") == publisher_wallet.address\n    assert transfer_event.args.to == consumer_wallet.address\n    assert data_nft.balanceOf(consumer_wallet.address) == 1\n    assert data_nft.balanceOf(publisher_wallet.address) == 0\n    assert data_nft.isERC20Deployer(consumer_wallet.address)\n    assert data_nft.ownerOf(1) == consumer_wallet.address\n\n    # Consumer is not the additional ERC20 deployer, but will be after the NFT transfer\n    data_nft = data_nft_factory.create({\"from\": publisher_wallet}, \"NFT1\", \"NFT\")\n\n    receipt = data_nft.safeTransferFrom(\n        publisher_wallet.address,\n        consumer_wallet.address,\n        1,\n        {\"from\": publisher_wallet},\n    )\n    transfer_event = data_nft.contract.events.Transfer().process_receipt(\n        receipt, errors=DISCARD\n    )[0]\n\n    assert getattr(transfer_event.args, \"from\") == publisher_wallet.address\n    assert transfer_event.args.to == consumer_wallet.address\n    assert data_nft.isERC20Deployer(consumer_wallet.address)\n\n    # Creates an ERC20\n    datatoken = data_nft.create_datatoken(\n        {\"from\": consumer_wallet},\n        \"DT1\",\n        \"DT1Symbol\",\n        publish_market_order_fees=TokenFeeInfo(\n            address=publisher_wallet.address,\n        ),\n    )\n    assert datatoken, \"Failed to create ERC20 token.\"\n\n    assert not datatoken.isMinter(publisher_wallet.address)\n    assert datatoken.isMinter(consumer_wallet.address)\n    datatoken.addMinter(publisher_wallet.address, {\"from\": consumer_wallet})\n    assert datatoken.getPermissions(publisher_wallet.address)[\n        0\n    ]  # publisher is minter now\n\n    OCEAN = publisher_ocean.OCEAN_token\n    OCEAN.approve(factory_router.address, to_wei(10000), {\"from\": consumer_wallet})\n\n    # Make consumer the publish market order fee address instead of publisher\n    receipt = datatoken.setPublishingMarketFee(\n        consumer_wallet.address,\n        OCEAN.address,\n        to_wei(1),\n        {\"from\": publisher_wallet},\n    )\n\n    set_publishing_fee_event = (\n        datatoken.contract.events.PublishMarketFeeChanged().process_receipt(\n            receipt, errors=DISCARD\n        )[0]\n    )\n    assert set_publishing_fee_event, \"Cannot find PublishMarketFeeChanged event.\"\n\n    publish_fees = datatoken.get_publish_market_order_fees()\n    assert publish_fees.address == consumer_wallet.address\n    assert publish_fees.token == OCEAN.address\n    assert publish_fees.amount == to_wei(1)\n\n\ndef test_nft_transfer_with_fre(\n    config,\n    OCEAN,\n    publisher_wallet,\n    consumer_wallet,\n    data_NFT_and_DT,\n):\n    \"\"\"Tests transferring the NFT before deploying an ERC20, a FRE.\"\"\"\n    data_nft, datatoken = data_NFT_and_DT\n\n    assert datatoken.isMinter(publisher_wallet.address)\n\n    # The NFT owner (publisher) has ERC20 deployer role & can deploy an exchange\n    exchange = datatoken.create_exchange(\n        rate=to_wei(1),\n        base_token_addr=OCEAN.address,\n        publish_market_fee=to_wei(0.01),\n        tx_dict={\"from\": publisher_wallet},\n    )\n\n    # Exchange should have supply and fees setup\n    # (Don't test thoroughly here, since exchange has its own unit tests)\n    details = exchange.details\n    assert details.owner == publisher_wallet.address\n    assert details.datatoken == datatoken.address\n    assert details.fixed_rate == to_wei(1)\n\n    # Now do a transfer\n    receipt = data_nft.safeTransferFrom(\n        publisher_wallet.address,\n        consumer_wallet.address,\n        1,\n        {\"from\": publisher_wallet},\n    )\n\n    transfer_event = data_nft.contract.events.Transfer().process_receipt(\n        receipt, errors=DISCARD\n    )[0]\n\n    assert getattr(transfer_event.args, \"from\") == publisher_wallet.address\n    assert transfer_event.args.to == consumer_wallet.address\n    assert data_nft.balanceOf(consumer_wallet) == 1\n    assert data_nft.balanceOf(publisher_wallet) == 0\n    assert data_nft.isERC20Deployer(consumer_wallet)\n    assert data_nft.ownerOf(1) == consumer_wallet.address\n    permissions = datatoken.getPermissions(consumer_wallet)\n    assert not permissions[0]  # the newest owner is not the minter\n    datatoken.addMinter(consumer_wallet, {\"from\": consumer_wallet})\n    assert datatoken.permissions(consumer_wallet)[0]\n\n    # Consumer wallet has not become the owner of the publisher's exchange\n    details = exchange.details\n    assert details.owner == publisher_wallet.address\n    assert details.active\n\n\n@pytest.mark.unit\ndef test_fail_create_datatoken(\n    config, publisher_wallet, consumer_wallet, another_consumer_wallet, data_nft_factory\n):\n    \"\"\"Tests multiple failures for creating ERC20 token.\"\"\"\n    data_nft = data_nft_factory.create({\"from\": publisher_wallet}, \"DT1\", \"DTSYMBOL\")\n    data_nft.addToCreateERC20List(consumer_wallet.address, {\"from\": publisher_wallet})\n\n    # Should fail to create a specific ERC20 Template if the index is ZERO\n    with pytest.raises(Exception, match=\"Template index doesnt exist\"):\n        data_nft.create_datatoken(\n            {\"from\": consumer_wallet},\n            template_index=0,\n            name=\"DT1\",\n            symbol=\"DT1Symbol\",\n        )\n\n    # Should fail to create a specific ERC20 Template if the index doesn't exist\n    with pytest.raises(Exception, match=\"Template index doesnt exist\"):\n        data_nft.create_datatoken(\n            {\"from\": consumer_wallet},\n            template_index=4,\n            name=\"DT1\",\n            symbol=\"DT1Symbol\",\n        )\n\n    # Should fail to create a specific ERC20 Template if the user is not added on the ERC20 deployers list\n    assert data_nft.getPermissions(another_consumer_wallet.address)[1] is False\n    with pytest.raises(Exception, match=\"NOT ERC20DEPLOYER_ROLE\"):\n        data_nft.create_datatoken(\n            {\"from\": another_consumer_wallet},\n            template_index=1,\n            name=\"DT1\",\n            symbol=\"DT1Symbol\",\n        )\n\n\n@pytest.mark.unit\ndef test_datatoken_cap(publisher_wallet, consumer_wallet, data_nft_factory):\n    # create NFT with ERC20\n    with pytest.raises(Exception, match=\"Cap is needed for Datatoken Template 2\"):\n        DatatokenArguments(template_index=2, name=\"DTB1\", symbol=\"EntDT1Symbol\")\n\n\n@pytest.mark.unit\ndef test_nft_owner_transfer(config, publisher_wallet, consumer_wallet, data_NFT_and_DT):\n    \"\"\"Test erc721 ownership transfer on token transfer\"\"\"\n    data_nft, datatoken = data_NFT_and_DT\n\n    assert data_nft.ownerOf(1) == publisher_wallet.address\n\n    with pytest.raises(Exception, match=\"transfer of token that is not own\"):\n        data_nft.transferFrom(\n            consumer_wallet.address,\n            publisher_wallet.address,\n            1,\n            {\"from\": publisher_wallet},\n        )\n    data_nft.transferFrom(\n        publisher_wallet.address, consumer_wallet.address, 1, {\"from\": publisher_wallet}\n    )\n\n    assert data_nft.balanceOf(publisher_wallet.address) == 0\n    assert data_nft.ownerOf(1) == consumer_wallet.address\n    # Owner is not NFT owner anymore, nor has any other role, neither older users\n    with pytest.raises(Exception, match=\"NOT ERC20DEPLOYER_ROLE\"):\n        data_nft.create_datatoken(\n            {\"from\": publisher_wallet},\n            name=\"DT1\",\n            symbol=\"DT1Symbol\",\n        )\n\n    with pytest.raises(Exception, match=\"NOT MINTER\"):\n        datatoken.mint(publisher_wallet.address, 10, {\"from\": publisher_wallet})\n\n    # NewOwner now owns the NFT, is already Manager by default and has all roles\n    data_nft.create_datatoken(\n        {\"from\": consumer_wallet},\n        name=\"DT1\",\n        symbol=\"DT1Symbol\",\n    )\n    datatoken.addMinter(consumer_wallet.address, {\"from\": consumer_wallet})\n\n    datatoken.mint(consumer_wallet.address, 20, {\"from\": consumer_wallet})\n\n    assert datatoken.balanceOf(consumer_wallet.address) == 20\n\n\ndef test_set_get_data(data_nft, alice):\n    # Key-value pair\n    key = \"fav_color\"\n    value = \"blue\"\n\n    # set data\n    data_nft.set_data(key, value, {\"from\": alice})\n\n    # retrieve data\n    value2 = data_nft.get_data(key)\n\n    # test\n    assert value2 == value\n"
  },
  {
    "path": "ocean_lib/models/test/test_data_nft_factory.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\nfrom web3.logs import DISCARD\nfrom web3.main import Web3\n\nfrom ocean_lib.models.data_nft import DataNFT, DataNFTArguments\nfrom ocean_lib.models.datatoken1 import Datatoken1\nfrom ocean_lib.models.datatoken_base import (\n    DatatokenArguments,\n    DatatokenBase,\n    TokenFeeInfo,\n)\nfrom ocean_lib.models.dispenser import Dispenser, DispenserArguments\nfrom ocean_lib.models.fixed_rate_exchange import ExchangeArguments\nfrom ocean_lib.ocean.util import create_checksum, get_address_of_type, to_wei\nfrom ocean_lib.structures.abi_tuples import OrderData, ReuseOrderData\nfrom ocean_lib.web3_internal.constants import ZERO_ADDRESS\nfrom ocean_lib.web3_internal.utils import split_signature\nfrom tests.resources.helper_functions import get_non_existent_nft_template\n\n\n@pytest.mark.unit\ndef test_nft_creation(\n    config,\n    publisher_wallet,\n    consumer_wallet,\n    data_nft_factory,\n):\n    \"\"\"Tests the utils functions.\"\"\"\n    data_nft = data_nft_factory.create({\"from\": publisher_wallet}, \"DT1\", \"DTSYMBOL\")\n    assert data_nft.name() == \"DT1\"\n    assert data_nft.symbol() == \"DTSYMBOL\"\n\n    # Tests current NFT count\n    current_nft_count = data_nft_factory.getCurrentNFTCount()\n    data_nft = data_nft_factory.create({\"from\": publisher_wallet}, \"DT2\", \"DTSYMBOL1\")\n    assert data_nft_factory.getCurrentNFTCount() == current_nft_count + 1\n\n    # Tests get NFT template\n    nft_template_address = get_address_of_type(config, DataNFT.CONTRACT_NAME, \"1\")\n    nft_template = data_nft_factory.getNFTTemplate(1)\n    assert nft_template[0] == nft_template_address\n    assert nft_template[1] is True\n\n    # Tests creating successfully an ERC20 token\n    data_nft.addToCreateERC20List(consumer_wallet.address, {\"from\": publisher_wallet})\n    datatoken = data_nft.create_datatoken(\n        {\"from\": publisher_wallet},\n        DatatokenArguments(\n            \"DT1P\",\n            \"DT1Symbol\",\n            fee_manager=consumer_wallet.address,\n        ),\n    )\n    assert datatoken, \"Failed to create ERC20 token.\"\n\n    # Tests templateCount function (one of them should be the Enterprise template)\n    assert data_nft_factory.templateCount() == 3\n\n    # Tests datatoken template list\n    datatoken_template_address = get_address_of_type(\n        config, Datatoken1.CONTRACT_NAME, \"1\"\n    )\n    template = data_nft_factory.getTokenTemplate(1)\n    assert template[0] == datatoken_template_address\n    assert template[1] is True\n\n    # Tests current token template (one of them should be the Enterprise template)\n    assert data_nft_factory.getCurrentTemplateCount() == 3\n\n\n@pytest.mark.unit\ndef test_combo_functions(\n    config,\n    publisher_wallet,\n    consumer_wallet,\n    data_nft_factory,\n):\n    \"\"\"Tests the utils functions.\"\"\"\n    # Tests creating NFT with ERC20 successfully\n    data_nft_token2, datatoken2 = data_nft_factory.create_with_erc20(\n        DataNFTArguments(\"72120Bundle\", \"72Bundle\"),\n        DatatokenArguments(\n            \"DTB1\",\n            \"DT1Symbol\",\n            fee_manager=consumer_wallet.address,\n        ),\n        {\"from\": publisher_wallet},\n    )\n    assert data_nft_token2.name() == \"72120Bundle\"\n    assert data_nft_token2.symbol() == \"72Bundle\"\n    assert datatoken2.name() == \"DTB1\"\n    assert datatoken2.symbol() == \"DT1Symbol\"\n\n    # Tests creating NFT with ERC20 and with Fixed Rate Exchange successfully.\n    fixed_rate_address = get_address_of_type(config, \"FixedPrice\")\n\n    # Create ERC20 data token for fees.\n    datatoken = data_nft_token2.create_datatoken(\n        {\"from\": publisher_wallet},\n        DatatokenArguments(\n            \"DT1P\",\n            \"DT1SymbolP\",\n            fee_manager=consumer_wallet.address,\n            publish_market_order_fees=TokenFeeInfo(\n                address=publisher_wallet.address,\n                token=ZERO_ADDRESS,\n                amount=to_wei(0.0005),\n            ),\n        ),\n    )\n    assert datatoken, \"Failed to create ERC20 token.\"\n    fee_datatoken_address = datatoken.address\n\n    (\n        data_nft_token4,\n        datatoken4,\n        one_fixed_rate,\n    ) = data_nft_factory.create_with_erc20_and_fixed_rate(\n        DataNFTArguments(\"72120Bundle\", \"72Bundle\"),\n        DatatokenArguments(\n            \"DTWithPool\",\n            \"DTP\",\n            fee_manager=consumer_wallet.address,\n        ),\n        ExchangeArguments(\n            base_token_addr=fee_datatoken_address,\n            publish_market_fee_collector=consumer_wallet.address,\n            rate=to_wei(1),\n            publish_market_fee=to_wei(0.001),\n            dt_decimals=18,\n        ),\n        tx_dict={\"from\": publisher_wallet},\n    )\n\n    assert data_nft_token4.name() == \"72120Bundle\"\n    assert data_nft_token4.symbol() == \"72Bundle\"\n    assert datatoken4.name() == \"DTWithPool\"\n    assert datatoken4.symbol() == \"DTP\"\n    assert one_fixed_rate.address == fixed_rate_address\n\n    # Tests creating NFT with ERC20 and with Dispenser successfully.\n    dispenser_address = get_address_of_type(config, Dispenser.CONTRACT_NAME)\n\n    data_nft_token5, datatoken5 = data_nft_factory.create_with_erc20_and_dispenser(\n        DataNFTArguments(\"72120Bundle\", \"72Bundle\"),\n        DatatokenArguments(\n            \"DTWithPool\",\n            \"DTP\",\n            fee_manager=consumer_wallet.address,\n        ),\n        DispenserArguments(to_wei(1), to_wei(1)),\n        tx_dict={\"from\": publisher_wallet},\n    )\n    assert data_nft_token5.name() == \"72120Bundle\"\n    assert data_nft_token5.symbol() == \"72Bundle\"\n    assert datatoken5.name() == \"DTWithPool\"\n    assert datatoken5.symbol() == \"DTP\"\n\n    _ = Dispenser(config, dispenser_address)\n\n    # Create a new erc721 with metadata in one single call and get address\n    data_nft = data_nft_factory.create_with_metadata(\n        DataNFTArguments(\"72120Bundle\", \"72Bundle\"),\n        metadata_state=1,\n        metadata_decryptor_url=\"http://myprovider:8030\",\n        metadata_decryptor_address=b\"0x123\",\n        metadata_flags=bytes(0),\n        metadata_data=Web3.to_hex(text=\"my cool metadata.\"),\n        metadata_data_hash=create_checksum(\"my cool metadata.\"),\n        metadata_proofs=[],\n        tx_dict={\"from\": publisher_wallet},\n    )\n    assert (\n        data_nft.name() == \"72120Bundle\"\n    ), \"NFT name doesn't match with the expected one.\"\n    metadata_info = data_nft.getMetaData()\n    assert metadata_info[3] is True\n    assert metadata_info[0] == \"http://myprovider:8030\"\n\n\n@pytest.mark.unit\ndef test_start_multiple_order(\n    config, publisher_wallet, consumer_wallet, another_consumer_wallet, data_nft_factory\n):\n    \"\"\"Tests the utils functions.\"\"\"\n    data_nft = data_nft_factory.create({\"from\": publisher_wallet}, \"DT1\", \"DTSYMBOL\")\n    assert data_nft.name() == \"DT1\"\n    assert data_nft.symbol() == \"DTSYMBOL\"\n    assert data_nft_factory.check_nft(data_nft.address)\n\n    # Tests current NFT count\n    current_nft_count = data_nft_factory.getCurrentNFTCount()\n    data_nft = data_nft_factory.create(\n        {\"from\": publisher_wallet}, DataNFTArguments(\"DT2\", \"DTSYMBOL1\")\n    )\n    assert data_nft.name() == \"DT2\"\n    assert data_nft_factory.getCurrentNFTCount() == current_nft_count + 1\n\n    # Tests get NFT template\n    nft_template_address = get_address_of_type(config, DataNFT.CONTRACT_NAME, \"1\")\n    nft_template = data_nft_factory.getNFTTemplate(1)\n    assert nft_template[0] == nft_template_address\n    assert nft_template[1] is True\n\n    # Tests creating successfully an ERC20 token\n    data_nft.addToCreateERC20List(consumer_wallet.address, {\"from\": publisher_wallet})\n    datatoken = data_nft.create_datatoken(\n        {\"from\": consumer_wallet},\n        DatatokenArguments(\n            name=\"DT1\",\n            symbol=\"DT1Symbol\",\n            minter=publisher_wallet.address,\n        ),\n    )\n    assert datatoken, \"Failed to create ERC20 token.\"\n\n    # Tests templateCount function (one of them should be the Enterprise template)\n    assert data_nft_factory.templateCount() == 3\n\n    # Tests datatoken template list\n    datatoken_template_address = get_address_of_type(\n        config, Datatoken1.CONTRACT_NAME, \"1\"\n    )\n    template = data_nft_factory.getTokenTemplate(1)\n    assert template[0] == datatoken_template_address\n    assert template[1] is True\n\n    # Tests current token template (one of them should be the Enterprise template)\n    assert data_nft_factory.getCurrentTemplateCount() == 3\n\n    # Tests datatoken can be checked as deployed by the factory\n    assert data_nft_factory.check_datatoken(datatoken.address)\n\n    # Tests starting multiple token orders successfully\n    datatoken = DatatokenBase.get_typed(config, datatoken.address)\n    dt_amount = to_wei(2)\n    mock_dai_contract_address = get_address_of_type(config, \"MockDAI\")\n    assert datatoken.balanceOf(consumer_wallet.address) == 0\n\n    datatoken.addMinter(consumer_wallet.address, {\"from\": publisher_wallet})\n    datatoken.mint(consumer_wallet.address, dt_amount, {\"from\": consumer_wallet})\n    assert datatoken.balanceOf(consumer_wallet.address) == dt_amount\n\n    datatoken.approve(data_nft_factory.address, dt_amount, {\"from\": consumer_wallet})\n\n    datatoken.setPaymentCollector(\n        another_consumer_wallet.address, {\"from\": publisher_wallet}\n    )\n\n    provider_fee_token = mock_dai_contract_address\n    provider_fee_amount = 0\n    provider_fee_address = publisher_wallet.address\n    # provider_data = json.dumps({\"timeout\": 0}, separators=(\",\", \":\"))\n    provider_data = b\"\\x00\"\n\n    message = Web3.solidity_keccak(\n        [\"bytes\", \"address\", \"address\", \"uint256\", \"uint256\"],\n        [\n            provider_data,\n            provider_fee_address,\n            provider_fee_token,\n            provider_fee_amount,\n            0,\n        ],\n    )\n    signed = config[\"web3_instance\"].eth.sign(provider_fee_address, data=message)\n    signature = split_signature(signed)\n\n    order_data = OrderData(\n        datatoken.address,\n        consumer_wallet.address,\n        1,\n        (\n            provider_fee_address,\n            provider_fee_token,\n            provider_fee_amount,\n            signature.v,\n            signature.r,\n            signature.s,\n            0,\n            provider_data,\n        ),\n        (\n            consumer_wallet.address,\n            mock_dai_contract_address,\n            0,\n        ),\n    )\n\n    orders = [order_data, order_data]\n\n    receipt = data_nft_factory.start_multiple_token_order(\n        orders, {\"from\": consumer_wallet}\n    )\n\n    registered_erc20_start_order_event = (\n        datatoken.contract.events.OrderStarted().process_receipt(\n            receipt, errors=DISCARD\n        )[0]\n    )\n\n    assert receipt, \"Failed starting multiple token orders.\"\n    assert registered_erc20_start_order_event.args.consumer == consumer_wallet.address\n\n    assert datatoken.balanceOf(consumer_wallet.address) == 0\n    assert datatoken.balanceOf(datatoken.getPaymentCollector()) == (dt_amount * 0.97)\n\n    reuse_order = ReuseOrderData(\n        order_data.token_address,\n        receipt.transactionHash,\n        (\n            provider_fee_address,\n            provider_fee_token,\n            provider_fee_amount,\n            signature.v,\n            signature.r,\n            signature.s,\n            0,\n            provider_data,\n        ),\n    )\n    receipt = data_nft_factory.reuse_multiple_token_order(\n        [reuse_order], {\"from\": consumer_wallet}\n    )\n\n    reused_event = datatoken.contract.events.OrderReused().process_receipt(\n        receipt, errors=DISCARD\n    )[0]\n    assert reused_event\n\n\n@pytest.mark.unit\ndef test_fail_get_templates(data_nft_factory):\n    \"\"\"Tests multiple failures for getting tokens' templates.\"\"\"\n    # Should fail to get the Datatoken template if index = 0\n    with pytest.raises(Exception, match=\"Template index doesnt exist\"):\n        data_nft_factory.getTokenTemplate(0)\n\n    # Should fail to get the Datatoken template if index > templateCount\n    with pytest.raises(Exception, match=\"Template index doesnt exist\"):\n        data_nft_factory.getTokenTemplate(4)\n\n\n@pytest.mark.unit\ndef test_nonexistent_template_index(data_nft_factory, publisher_wallet):\n    \"\"\"Test erc721 non existent template creation fail\"\"\"\n    non_existent_nft_template = get_non_existent_nft_template(\n        data_nft_factory, check_first=10\n    )\n    assert non_existent_nft_template >= 0, \"Non existent NFT template not found.\"\n\n    with pytest.raises(Exception, match=\"Template index doesnt exist\"):\n        data_nft_factory.create(\n            {\"from\": publisher_wallet},\n            DataNFTArguments(\n                \"DT1\", \"DTSYMBOL\", template_index=non_existent_nft_template\n            ),\n        )\n"
  },
  {
    "path": "ocean_lib/models/test/test_datatoken.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\nfrom web3.logs import DISCARD\nfrom web3.main import Web3\n\nfrom ocean_lib.models.datatoken_base import DatatokenRoles, TokenFeeInfo\nfrom ocean_lib.ocean.util import get_address_of_type, to_wei\nfrom ocean_lib.web3_internal.constants import MAX_UINT256\nfrom tests.resources.helper_functions import get_mock_provider_fees\n\n\n@pytest.mark.unit\ndef test_main(\n    publisher_wallet, consumer_wallet, another_consumer_wallet, data_NFT_and_DT\n):\n    \"\"\"Tests successful function calls\"\"\"\n    data_nft, datatoken = data_NFT_and_DT\n\n    # Check datatoken params\n    assert datatoken.getId() == 1\n    assert datatoken.name() == \"DT1\"\n    assert datatoken.symbol() == \"DT1Symbol\"\n    assert datatoken.decimals() == 18\n    assert datatoken.cap() == MAX_UINT256\n\n    # Check data NFT address\n    assert datatoken.getERC721Address() == data_nft.address\n\n    # Check that the Datatoken contract is initialized\n    assert datatoken.isInitialized()\n\n    # Check publish market payment collector\n    assert datatoken.getPaymentCollector() == publisher_wallet.address\n\n    # Set payment collector to consumer\n    datatoken.setPaymentCollector(\n        consumer_wallet.address,\n        {\"from\": publisher_wallet},\n    )\n    assert datatoken.getPaymentCollector() == consumer_wallet.address\n\n    # Check minter permissions\n    assert datatoken.getPermissions(publisher_wallet.address)[DatatokenRoles.MINTER]\n    assert datatoken.isMinter(publisher_wallet.address)\n\n    # Mint Datatoken to user2 from publisher\n    datatoken.mint(consumer_wallet.address, 1, {\"from\": publisher_wallet})\n    assert datatoken.balanceOf(consumer_wallet.address) == 1\n\n    # Add minter\n    assert not datatoken.getPermissions(consumer_wallet.address)[DatatokenRoles.MINTER]\n    datatoken.addMinter(consumer_wallet.address, {\"from\": publisher_wallet})\n    assert datatoken.getPermissions(consumer_wallet.address)[DatatokenRoles.MINTER]\n\n    # Mint Datatoken to user2 from consumer\n    datatoken.mint(consumer_wallet.address, 1, {\"from\": consumer_wallet})\n    assert datatoken.balanceOf(consumer_wallet.address) == 2\n\n    # Should succeed to removeMinter if erc20Deployer\n    datatoken.removeMinter(consumer_wallet.address, {\"from\": publisher_wallet})\n    assert not datatoken.getPermissions(consumer_wallet.address)[DatatokenRoles.MINTER]\n\n    # Should succeed to addFeeManager if erc20Deployer (permission to deploy the erc20Contract at 721 level)\n    assert not datatoken.getPermissions(consumer_wallet.address)[\n        DatatokenRoles.PAYMENT_MANAGER\n    ]\n    datatoken.addPaymentManager(consumer_wallet.address, {\"from\": publisher_wallet})\n    assert datatoken.getPermissions(consumer_wallet.address)[\n        DatatokenRoles.PAYMENT_MANAGER\n    ]\n\n    # Should succeed to removeFeeManager if erc20Deployer\n    datatoken.removePaymentManager(consumer_wallet.address, {\"from\": publisher_wallet})\n    assert not datatoken.getPermissions(consumer_wallet.address)[\n        DatatokenRoles.PAYMENT_MANAGER\n    ]\n\n    # Should succeed to setData if erc20Deployer\n    value = Web3.to_hex(text=\"SomeData\")\n    key = Web3.keccak(hexstr=datatoken.address)\n\n    datatoken.setData(value, {\"from\": publisher_wallet})\n\n    assert Web3.to_hex(data_nft.getData(key)) == value\n\n    # Should succeed to call cleanPermissions if NFTOwner\n    datatoken.cleanPermissions({\"from\": publisher_wallet})\n\n    permissions = datatoken.getPermissions(publisher_wallet.address)\n    assert not permissions[DatatokenRoles.MINTER]\n    assert not permissions[DatatokenRoles.PAYMENT_MANAGER]\n\n    with pytest.raises(Exception, match=\"NOT ERC20DEPLOYER_ROLE\"):\n        data_nft.create_datatoken(\n            {\"from\": another_consumer_wallet},\n            name=\"DT1\",\n            symbol=\"DT1Symbol\",\n        )\n\n\ndef test_start_order(config, publisher_wallet, consumer_wallet, data_NFT_and_DT):\n    \"\"\"Tests startOrder functionality without publish fees, consume fees.\"\"\"\n    data_nft, datatoken = data_NFT_and_DT\n    # Mint datatokens to use\n    datatoken.mint(consumer_wallet.address, to_wei(10), {\"from\": publisher_wallet})\n    datatoken.mint(publisher_wallet.address, to_wei(10), {\"from\": publisher_wallet})\n\n    # Set the fee collector address\n    datatoken.setPaymentCollector(\n        get_address_of_type(config, \"OPFCommunityFeeCollector\"),\n        {\"from\": publisher_wallet},\n    )\n\n    provider_fees = get_mock_provider_fees(\"MockUSDC\", publisher_wallet)\n\n    receipt = datatoken.start_order(\n        consumer=consumer_wallet.address,\n        service_index=1,\n        provider_fees=provider_fees,\n        consume_market_fees=TokenFeeInfo(\n            address=publisher_wallet.address,\n            token=datatoken.address,\n        ),\n        tx_dict={\"from\": publisher_wallet},\n    )\n    # Check erc20 balances\n    assert datatoken.balanceOf(publisher_wallet.address) == to_wei(9)\n    assert datatoken.balanceOf(\n        get_address_of_type(config, \"OPFCommunityFeeCollector\")\n    ) == to_wei(1)\n\n    provider_fee_address = publisher_wallet.address\n    provider_data = provider_fees[\"providerData\"]\n    provider_message = Web3.solidity_keccak(\n        [\"bytes32\", \"bytes\"],\n        [receipt.transactionHash, provider_data],\n    )\n    provider_signed = config[\"web3_instance\"].eth.sign(\n        provider_fee_address, data=provider_message\n    )\n\n    message = Web3.solidity_keccak(\n        [\"bytes\"],\n        [Web3.to_hex(Web3.to_bytes(text=\"12345\"))],\n    )\n    consumer_signed = config[\"web3_instance\"].eth.sign(\n        consumer_wallet.address, data=message\n    )\n\n    receipt_interm = datatoken.orderExecuted(\n        receipt.transactionHash,\n        provider_data,\n        provider_signed,\n        Web3.to_hex(Web3.to_bytes(text=\"12345\")),\n        consumer_signed,\n        consumer_wallet.address,\n        {\"from\": publisher_wallet},\n    )\n    executed_event = datatoken.contract.events.OrderExecuted().process_receipt(\n        receipt_interm, errors=DISCARD\n    )[0]\n    assert executed_event.args.orderTxId == receipt.transactionHash\n    assert executed_event.args.providerAddress == provider_fee_address\n\n    # Tests exceptions for order_executed\n    consumer_signed = config[\"web3_instance\"].eth.sign(\n        provider_fee_address, data=message\n    )\n    with pytest.raises(Exception, match=\"Consumer signature check failed\"):\n        datatoken.orderExecuted(\n            receipt.transactionHash,\n            provider_data,\n            provider_signed,\n            Web3.to_hex(Web3.to_bytes(text=\"12345\")),\n            consumer_signed,\n            consumer_wallet.address,\n            {\"from\": publisher_wallet},\n        )\n\n    message = Web3.solidity_keccak(\n        [\"bytes\"],\n        [Web3.to_hex(Web3.to_bytes(text=\"12345\"))],\n    )\n    consumer_signed = config[\"web3_instance\"].eth.sign(\n        consumer_wallet.address, data=message\n    )\n\n    with pytest.raises(Exception, match=\"Provider signature check failed\"):\n        datatoken.orderExecuted(\n            receipt.transactionHash,\n            provider_data,\n            consumer_signed,\n            Web3.to_hex(Web3.to_bytes(text=\"12345\")),\n            consumer_signed,\n            consumer_wallet.address,\n            {\"from\": publisher_wallet},\n        )\n\n    # Tests reuses order\n    receipt_interm = datatoken.reuse_order(\n        receipt.transactionHash,\n        provider_fees=provider_fees,\n        tx_dict={\"from\": publisher_wallet},\n    )\n    reused_event = datatoken.contract.events.OrderReused().process_receipt(\n        receipt_interm, errors=DISCARD\n    )[0]\n    assert reused_event, \"Cannot find OrderReused event\"\n    assert reused_event.args.orderTxId == receipt.transactionHash\n    assert reused_event.args.caller == publisher_wallet.address\n\n    provider_fee_event = datatoken.contract.events.ProviderFee().process_receipt(\n        receipt_interm, errors=DISCARD\n    )[0]\n    assert provider_fee_event, \"Cannot find ProviderFee event\"\n\n    # Set and get publishing market fee params\n    datatoken.setPublishingMarketFee(\n        publisher_wallet.address,\n        get_address_of_type(config, \"MockUSDC\"),\n        to_wei(1.2),\n        {\"from\": publisher_wallet},\n    )\n\n    publish_fees = datatoken.get_publish_market_order_fees()\n\n    # PublishMarketFeeAddress set previously\n    assert publish_fees.address == publisher_wallet.address\n    # PublishMarketFeeToken set previously\n    assert publish_fees.token == get_address_of_type(config, \"MockUSDC\")\n    # PublishMarketFeeAmount set previously\n    assert publish_fees.amount == to_wei(1.2)\n    # Fee collector\n    assert datatoken.getPaymentCollector() == get_address_of_type(\n        config, \"OPFCommunityFeeCollector\"\n    )\n\n    # Publisher should succeed to burn some consumer's tokens using burnFrom\n    initial_total_supply = datatoken.totalSupply()\n    initial_consumer_balance = datatoken.balanceOf(consumer_wallet.address)\n\n    # Approve publisher to burn\n    datatoken.approve(publisher_wallet.address, to_wei(10), {\"from\": consumer_wallet})\n\n    allowance = datatoken.allowance(consumer_wallet.address, publisher_wallet.address)\n    assert allowance == to_wei(10)\n    datatoken.burnFrom(consumer_wallet.address, to_wei(2), {\"from\": publisher_wallet})\n\n    assert datatoken.totalSupply() == initial_total_supply - to_wei(2)\n    assert datatoken.balanceOf(\n        consumer_wallet.address\n    ) == initial_consumer_balance - to_wei(2)\n\n    # Test transterFrom too\n    initial_consumer_balance = datatoken.balanceOf(consumer_wallet.address)\n    datatoken.transferFrom(\n        consumer_wallet.address,\n        publisher_wallet.address,\n        to_wei(1),\n        {\"from\": publisher_wallet},\n    )\n    assert datatoken.balanceOf(\n        consumer_wallet.address\n    ) == initial_consumer_balance - to_wei(1)\n\n    # Consumer should be able to burn his tokens too\n    initial_consumer_balance = datatoken.balanceOf(consumer_wallet.address)\n    datatoken.burn(to_wei(1), {\"from\": consumer_wallet})\n    assert datatoken.balanceOf(\n        consumer_wallet.address\n    ) == initial_consumer_balance - to_wei(1)\n\n    # Consumer should be able to transfer too\n    initial_consumer_balance = datatoken.balanceOf(consumer_wallet.address)\n    datatoken.transfer(publisher_wallet.address, to_wei(1), {\"from\": consumer_wallet})\n    assert datatoken.balanceOf(\n        consumer_wallet.address\n    ) == initial_consumer_balance - to_wei(1)\n\n\n@pytest.mark.unit\ndef test_exceptions(consumer_wallet, config, publisher_wallet, DT):\n    \"\"\"Tests revert statements in contracts functions\"\"\"\n    datatoken = DT\n\n    # Should fail to mint if wallet is not a minter\n    with pytest.raises(Exception, match=\"NOT MINTER\"):\n        datatoken.mint(\n            consumer_wallet.address,\n            to_wei(1),\n            {\"from\": consumer_wallet},\n        )\n\n    #  Should fail to set new FeeCollector if not NFTOwner\n    with pytest.raises(Exception, match=\"NOT PAYMENT MANAGER or OWNER\"):\n        datatoken.setPaymentCollector(\n            consumer_wallet.address,\n            {\"from\": consumer_wallet},\n        )\n\n    # Should fail to addMinter if not erc20Deployer (permission to deploy the erc20Contract at 721 level)\n    with pytest.raises(Exception, match=\"NOT DEPLOYER ROLE\"):\n        datatoken.addMinter(consumer_wallet.address, {\"from\": consumer_wallet})\n\n    #  Should fail to removeMinter even if it's minter\n    with pytest.raises(Exception, match=\"NOT DEPLOYER ROLE\"):\n        datatoken.removeMinter(consumer_wallet.address, {\"from\": consumer_wallet})\n\n    # Should fail to addFeeManager if not erc20Deployer (permission to deploy the erc20Contract at 721 level)\n    with pytest.raises(Exception, match=\"NOT DEPLOYER ROLE\"):\n        datatoken.addPaymentManager(consumer_wallet.address, {\"from\": consumer_wallet})\n\n    # Should fail to removeFeeManager if NOT erc20Deployer\n    with pytest.raises(Exception, match=\"NOT DEPLOYER ROLE\"):\n        datatoken.removePaymentManager(\n            consumer_wallet.address, {\"from\": consumer_wallet}\n        )\n\n    # Should fail to setData if NOT erc20Deployer\n    with pytest.raises(Exception, match=\"NOT DEPLOYER ROLE\"):\n        datatoken.setData(Web3.to_hex(text=\"SomeData\"), {\"from\": consumer_wallet})\n\n    # Should fail to call cleanPermissions if NOT NFTOwner\n    with pytest.raises(Exception, match=\"not NFTOwner\"):\n        datatoken.cleanPermissions({\"from\": consumer_wallet})\n\n    # Clean from nft should work shouldn't be callable by publisher or consumer, only by erc721 contract\n    with pytest.raises(Exception, match=\"NOT 721 Contract\"):\n        datatoken.cleanFrom721({\"from\": consumer_wallet})\n"
  },
  {
    "path": "ocean_lib/models/test/test_datatoken_order_both_templates.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom datetime import datetime\n\nimport pytest\n\nfrom ocean_lib.models.datatoken_base import DatatokenBase, TokenFeeInfo\nfrom ocean_lib.ocean.util import from_wei, get_address_of_type, to_wei\nfrom ocean_lib.web3_internal.constants import MAX_UINT256\nfrom tests.resources.helper_functions import deploy_erc721_erc20, get_mock_provider_fees\n\nvalid_until = int(datetime(2032, 12, 31).timestamp())\n\n\n@pytest.mark.unit\ndef test_dispense_and_order_with_non_defaults(\n    config,\n    publisher_wallet,\n    consumer_wallet,\n    factory_deployer_wallet,\n):\n    \"\"\"Tests dispense_and_order function of the Datatoken Template 2\"\"\"\n    _, DT = deploy_erc721_erc20(config, publisher_wallet, publisher_wallet, 2)\n\n    USDC = DatatokenBase.get_typed(config, get_address_of_type(config, \"MockUSDC\"))\n    DAI = DatatokenBase.get_typed(config, get_address_of_type(config, \"MockDAI\"))\n\n    _ = DT.create_dispenser(\n        max_tokens=to_wei(1), max_balance=to_wei(1), tx_dict={\"from\": publisher_wallet}\n    )\n\n    status = DT.dispenser_status()\n\n    assert status.active\n    assert status.owner_address == publisher_wallet.address\n    assert status.is_minter\n\n    # ALLOWED_SWAPPER == ZERO means anyone should be able to request dispense\n    # However, ERC20TemplateEnterprise.sol has a quirk where this isn't allowed\n    # Below, we test the quirk.\n    match_s = \"This address is not allowed to request DT\"\n    with pytest.raises(Exception, match=match_s):\n        DT.dispense(to_wei(1), {\"from\": consumer_wallet})\n\n    consume_fee_amount = to_wei(2)\n    consume_fee_address = consumer_wallet.address\n    DT.setPublishingMarketFee(\n        consume_fee_address,\n        USDC.address,\n        consume_fee_amount,\n        {\"from\": publisher_wallet},\n    )\n\n    publish_market_fees = DT.get_publish_market_order_fees()\n\n    USDC.transfer(\n        publisher_wallet.address,\n        publish_market_fees.amount,\n        {\"from\": factory_deployer_wallet},\n    )\n\n    USDC.approve(\n        DT.address,\n        consume_fee_amount,\n        {\"from\": publisher_wallet},\n    )\n\n    DAI.transfer(\n        publisher_wallet.address,\n        consume_fee_amount,\n        {\"from\": factory_deployer_wallet},\n    )\n\n    DAI.approve(\n        DT.address,\n        consume_fee_amount,\n        {\"from\": publisher_wallet},\n    )\n\n    provider_fees = get_mock_provider_fees(\n        \"MockDAI\", publisher_wallet, valid_until=valid_until\n    )\n\n    opf_collector_address = get_address_of_type(config, \"OPFCommunityFeeCollector\")\n\n    balance_opf_consume_before = DAI.balanceOf(opf_collector_address)\n    publish_bal_before = USDC.balanceOf(consumer_wallet.address)\n\n    tx = DT.dispense_and_order(\n        consumer=consume_fee_address,\n        provider_fees=provider_fees,\n        consume_market_fees=TokenFeeInfo(\n            address=consume_fee_address,\n            token=DAI.address,\n        ),\n        tx_dict={\"from\": publisher_wallet},\n    )\n\n    assert tx\n    assert DT.totalSupply() == to_wei(0)\n\n    balance_opf_consume = DAI.balanceOf(opf_collector_address)\n    balance_publish = USDC.balanceOf(publish_market_fees.address)\n\n    assert balance_opf_consume - balance_opf_consume_before == 0\n    assert balance_publish - publish_bal_before == to_wei(2)\n\n    assert DT.balanceOf(DT.getPaymentCollector()) == 0\n\n\n@pytest.mark.unit\n@pytest.mark.parametrize(\"template_index\", [1, 2])\ndef test_dispense_and_order_with_defaults(\n    config, publisher_wallet, consumer_wallet, factory_deployer_wallet, template_index\n):\n    \"\"\"Tests dispense_and_order function of the Datatoken1 and Datatoken2\"\"\"\n    _, DT = deploy_erc721_erc20(\n        config, publisher_wallet, publisher_wallet, template_index\n    )\n\n    _ = DT.create_dispenser(\n        max_tokens=to_wei(1),\n        max_balance=to_wei(1),\n        tx_dict={\"from\": publisher_wallet},\n    )\n\n    provider_fees = get_mock_provider_fees(\n        \"MockDAI\", publisher_wallet, valid_until=valid_until\n    )\n\n    tx = DT.dispense_and_order(\n        consumer=consumer_wallet.address,\n        provider_fees=provider_fees,\n        tx_dict={\"from\": publisher_wallet},\n    )\n\n    assert tx\n    assert DT.totalSupply() == (to_wei(0) if template_index == 2 else to_wei(1))\n\n\n@pytest.mark.unit\n@pytest.mark.parametrize(\"template_index\", [1, 2])\ndef test_buy_DT_and_order(\n    config,\n    publisher_wallet,\n    consumer_wallet,\n    factory_deployer_wallet,\n    another_consumer_wallet,\n    template_index,\n):\n    \"\"\"Tests buy_DT_and_order function of the Datatoken1 and Datatoken2\"\"\"\n    _, DT = deploy_erc721_erc20(\n        config, publisher_wallet, publisher_wallet, template_index\n    )\n\n    USDC = DatatokenBase.get_typed(config, get_address_of_type(config, \"MockUSDC\"))\n    DAI = DatatokenBase.get_typed(config, get_address_of_type(config, \"MockDAI\"))\n\n    exchange = DT.create_exchange(\n        rate=to_wei(1),\n        base_token_addr=USDC.address,\n        tx_dict={\"from\": publisher_wallet},\n        publish_market_fee=to_wei(0.1),\n    )\n    assert exchange.details.active\n    assert exchange.details.with_mint\n\n    if template_index == 2:\n        with pytest.raises(Exception, match=\"This address is not allowed to swap\"):\n            exchange.buy_DT(\n                datatoken_amt=to_wei(1),\n                max_basetoken_amt=to_wei(1),\n                tx_dict={\"from\": consumer_wallet},\n            )\n\n    consume_fee_amount = to_wei(2)\n    consume_fee_address = consumer_wallet.address\n    DT.setPublishingMarketFee(\n        consume_fee_address,\n        USDC.address,\n        consume_fee_amount,\n        {\"from\": publisher_wallet},\n    )\n\n    publish_market_fees = DT.get_publish_market_order_fees()\n\n    USDC.transfer(\n        publisher_wallet.address,\n        publish_market_fees.amount + to_wei(3),\n        {\"from\": factory_deployer_wallet},\n    )\n    USDC.approve(\n        DT.address,\n        MAX_UINT256,\n        {\"from\": publisher_wallet},\n    )\n    USDC.approve(\n        exchange.address,\n        MAX_UINT256,\n        {\"from\": publisher_wallet},\n    )\n    DAI.transfer(\n        publisher_wallet.address,\n        consume_fee_amount,\n        {\"from\": factory_deployer_wallet},\n    )\n    DAI.approve(DT.address, consume_fee_amount, {\"from\": publisher_wallet})\n\n    provider_fees = get_mock_provider_fees(\n        \"MockDAI\", publisher_wallet, valid_until=valid_until\n    )\n\n    consume_bal1 = DAI.balanceOf(consume_fee_address)\n    publish_bal1 = USDC.balanceOf(consumer_wallet.address)\n\n    args = {\n        \"consumer\": another_consumer_wallet.address,\n        \"provider_fees\": provider_fees,\n        \"consume_market_fees\": TokenFeeInfo(\n            address=consume_fee_address,\n            token=DAI.address,\n        ),\n        \"exchange\": exchange,\n        \"tx_dict\": {\"from\": publisher_wallet},\n    }\n\n    if template_index == 2:\n        args[\"max_base_token_amount\"] = to_wei(2.5)\n        args[\"consume_market_swap_fee_amount\"] = to_wei(0.001)  # 1e15 => 0.1%\n        args[\"consume_market_swap_fee_address\"] = another_consumer_wallet.address\n\n    tx = DT.buy_DT_and_order(**args)\n\n    assert tx\n\n    if template_index == 2:\n        assert DT.totalSupply() == to_wei(0)\n\n    consume_bal2 = DAI.balanceOf(consume_fee_address)\n    publish_bal2 = USDC.balanceOf(publish_market_fees.address)\n\n    assert from_wei(consume_bal2) == from_wei(consume_bal1)\n\n    assert from_wei(publish_bal2) == from_wei(publish_bal1) + 2.0\n\n    if template_index == 2:\n        assert from_wei(DT.balanceOf(DT.getPaymentCollector())) == 0\n"
  },
  {
    "path": "ocean_lib/models/test/test_dispenser.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\nfrom ocean_lib.models.dispenser import Dispenser, DispenserStatus\nfrom ocean_lib.ocean.util import from_wei, get_address_of_type, to_wei\nfrom ocean_lib.web3_internal.constants import MAX_UINT256, ZERO_ADDRESS\nfrom tests.resources.helper_functions import deploy_erc721_erc20\n\n\n@pytest.mark.unit\ndef test_DispenserStatus():\n    \"\"\"Test DispenserStatus object\"\"\"\n    # set status_tup\n    active = True\n    owner = \"0x1234\"\n    is_minter = True\n    max_tokens = 456\n    max_bal = 789\n    bal = 3\n    swpr = ZERO_ADDRESS  # allowed swapper. ZERO_ADDRESS = anyone can request\n\n    # create the object\n    status_tup = (active, owner, is_minter, max_tokens, max_bal, bal, swpr)\n    status = DispenserStatus(status_tup)\n\n    # verify status\n    assert isinstance(status, DispenserStatus)\n    assert status.active\n    assert status.owner_address == \"0x1234\"\n    assert status.is_minter is True\n    assert status.max_tokens == 456\n    assert status.max_balance == 789\n    assert status.balance == 3\n    assert status.allowed_swapper == ZERO_ADDRESS\n\n    # verify __str__\n    s = str(status)\n    assert \"DispenserStatus\" in s\n    assert \"active = True\" in s\n    assert \"owner_address = 0x1234\" in s\n    assert \"is_minter\" in s\n    assert \"max_tokens\" in s\n    assert \"max_balance\" in s\n    assert \"balance\" in s\n    assert \"allowed_swapper = anyone\" in s\n\n\n@pytest.mark.unit\ndef test_main_flow_via_simple_ux_and_good_defaults(\n    config,\n    publisher_wallet,\n    consumer_wallet,\n):\n    \"\"\"\n    Tests main flow, via simple interface Datatoken.create_dispenser().\n    Focus on the basic steps.\n    Use good defaults for max_tokens, max_balance, more.\n    \"\"\"\n    _, datatoken = deploy_erc721_erc20(config, publisher_wallet, publisher_wallet)\n\n    # basic steps\n    datatoken.create_dispenser({\"from\": publisher_wallet})\n    datatoken.dispense(to_wei(3), {\"from\": consumer_wallet})\n\n    # check balance\n    bal = datatoken.balanceOf(consumer_wallet.address)\n    assert from_wei(bal) == 3\n\n    # check status\n    status = datatoken.dispenser_status()\n    assert isinstance(status, DispenserStatus)\n    assert status.active\n    assert status.owner_address == publisher_wallet.address\n    assert status.is_minter is True\n    assert status.max_tokens == MAX_UINT256\n    assert status.max_balance == MAX_UINT256\n    assert status.balance == 0  # 0, not 3, because it mints on the fly\n    assert status.allowed_swapper == ZERO_ADDRESS  # anyone can request\n\n\n@pytest.mark.unit\ndef test_main_flow_via_simple_ux_and_setting_token_counts(\n    config,\n    publisher_wallet,\n    consumer_wallet,\n):\n    \"\"\"\n    Tests main flow, via simple interface Datatoken.create_dispenser().\n    Focus on the basic steps.\n    Set values for max_tokens, max_balance.\n    \"\"\"\n    _, datatoken = deploy_erc721_erc20(config, publisher_wallet, publisher_wallet)\n    # set params\n    max_tokens = to_wei(456)  # max # tokens to dispense\n    max_balance = to_wei(789)  # max balance of requester\n\n    # basic steps\n    datatoken.create_dispenser({\"from\": publisher_wallet}, max_tokens, max_balance)\n    datatoken.dispense(to_wei(3), {\"from\": consumer_wallet})\n\n    # check status\n    status = datatoken.dispenser_status()\n    assert from_wei(status.max_tokens) == 456\n    assert from_wei(status.max_balance) == 789\n    assert status.balance == 0\n\n\n@pytest.mark.unit\ndef test_main_flow_via_contract_directly(\n    config,\n    publisher_wallet,\n    consumer_wallet,\n    factory_deployer_wallet,\n):\n    \"\"\"\n    Tests main flow, via direct calls to smart contracts (more args).\n    Has not just basic steps, but also advanced actions.\n    \"\"\"\n    _, datatoken = deploy_erc721_erc20(config, publisher_wallet, publisher_wallet)\n\n    # get the dispenser\n    dispenser = Dispenser(config, get_address_of_type(config, \"Dispenser\"))\n\n    # Tests publisher creates a dispenser with minter role\n    _ = datatoken.create_dispenser({\"from\": publisher_wallet}, to_wei(1), to_wei(1))\n\n    # Tests publisher gets the dispenser status\n\n    dispenser_status = dispenser.status(datatoken.address)\n    assert dispenser_status[0] is True\n    assert dispenser_status[1] == publisher_wallet.address\n    assert dispenser_status[2] is True\n\n    # Tests consumer requests more datatokens then allowed transaction reverts\n    with pytest.raises(Exception, match=\"Amount too high\"):\n        dispenser.dispense(\n            datatoken.address,\n            to_wei(20),\n            consumer_wallet.address,\n            {\"from\": consumer_wallet},\n        )\n\n    # Tests consumer requests data tokens\n    _ = dispenser.dispense(\n        datatoken.address,\n        to_wei(1),\n        consumer_wallet.address,\n        {\"from\": consumer_wallet},\n    )\n\n    # Tests consumer requests more datatokens then exceeds maxBalance\n    with pytest.raises(Exception, match=\"Caller balance too high\"):\n        dispenser.dispense(\n            datatoken.address,\n            to_wei(1),\n            consumer_wallet.address,\n            {\"from\": consumer_wallet},\n        )\n\n    # Tests publisher deactivates the dispenser\n    dispenser.deactivate(datatoken.address, {\"from\": publisher_wallet})\n    status = dispenser.status(datatoken.address)\n    assert status[0] is False\n\n    # Tests factory deployer should fail to get data tokens\n    with pytest.raises(Exception, match=\"Dispenser not active\"):\n        dispenser.dispense(\n            datatoken.address,\n            to_wei(0.00001),\n            factory_deployer_wallet.address,\n            {\"from\": factory_deployer_wallet},\n        )\n\n    # Tests consumer should fail to activate a dispenser for a token for he is not a minter\n    with pytest.raises(Exception, match=\"Invalid owner\"):\n        dispenser.activate(\n            datatoken.address,\n            to_wei(1),\n            to_wei(1),\n            {\"from\": consumer_wallet},\n        )\n\n\ndef test_dispenser_creation_without_minter(config, publisher_wallet, consumer_wallet):\n    \"\"\"Tests dispenser creation without a minter role.\"\"\"\n    _, datatoken = deploy_erc721_erc20(config, publisher_wallet, publisher_wallet)\n\n    # get the dispenser\n    dispenser = Dispenser(config, get_address_of_type(config, \"Dispenser\"))\n\n    datatoken.create_dispenser(\n        {\"from\": publisher_wallet}, to_wei(1), to_wei(1), with_mint=False\n    )\n\n    # Tests consumer requests data tokens but they are not minted\n    with pytest.raises(Exception, match=\"Not enough reserves\"):\n        dispenser.dispense(\n            datatoken.address,\n            to_wei(1),\n            consumer_wallet.address,\n            {\"from\": consumer_wallet},\n        )\n\n    # Tests publisher mints tokens and transfer them to the dispenser.\n    datatoken.mint(\n        dispenser.address,\n        to_wei(1),\n        {\"from\": publisher_wallet},\n    )\n\n    # Tests consumer requests data tokens\n    dispenser.dispense(\n        datatoken.address,\n        to_wei(1),\n        consumer_wallet.address,\n        {\"from\": consumer_wallet},\n    )\n\n    # Tests publisher withdraws all datatokens\n    dispenser.ownerWithdraw(datatoken.address, {\"from\": publisher_wallet})\n\n    status = dispenser.status(datatoken.address)\n    assert status[5] == 0\n"
  },
  {
    "path": "ocean_lib/models/test/test_exchange_fees.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\nfrom web3.logs import DISCARD\n\nfrom ocean_lib.models.datatoken1 import Datatoken1\nfrom ocean_lib.models.factory_router import FactoryRouter\nfrom ocean_lib.models.fixed_rate_exchange import FixedRateExchange, OneExchange\nfrom ocean_lib.models.test.test_factory_router import (\n    OPC_SWAP_FEE_APPROVED,\n    OPC_SWAP_FEE_NOT_APPROVED,\n)\nfrom ocean_lib.ocean.util import from_wei, get_address_of_type, to_wei\nfrom ocean_lib.web3_internal.constants import MAX_UINT256, ZERO_ADDRESS\nfrom tests.resources.helper_functions import (\n    convert_bt_amt_to_dt,\n    get_wallet,\n    int_units,\n    transfer_bt_if_balance_lte,\n)\n\n\n@pytest.mark.unit\n@pytest.mark.parametrize(\n    \"bt_name, publish_market_swap_fee, consume_market_swap_fee, bt_per_dt\",\n    [\n        # Min fees\n        (\"Ocean\", 0, 0, 1),\n        (\"MockUSDC\", 0, 0, 1),\n        # Happy path\n        (\"Ocean\", 0.003, 0.005, 1),\n        (\"MockDAI\", 0.003, 0.005, 1),\n        (\"MockUSDC\", 0.003, 0.005, 1),\n        # Max fees\n        (\"Ocean\", 0.1, 0.1, 1),\n        (\"MockUSDC\", 0.1, 0.1, 1),\n        # Min rate. Rate must be => 1e10 wei\n        (\"Ocean\", 0.003, 0.005, 0.000000010000000000),\n        (\"MockUSDC\", 0.003, 0.005, 0.000000010000000000),\n        # High rate. There is no maximum\n        (\"Ocean\", 0.003, 0.005, 1000),\n        (\"MockUSDC\", 0.003, 0.005, 1000),\n    ],\n)\ndef test_exchange_swap_fees(\n    config: dict,\n    factory_deployer_wallet,\n    bob,\n    alice,\n    DT,\n    bt_name: str,\n    publish_market_swap_fee: str,\n    consume_market_swap_fee: str,\n    bt_per_dt: str,\n):\n    \"\"\"\n    Tests fixed rate exchange swap fees with OCEAN, DAI, and USDC as base token\n\n    OCEAN is an approved base token with 18 decimals (OPC Fee = 0.1%)\n    DAI is a non-approved base token with 18 decimals (OPC Fee = 0.2%)\n    USDC is a non-approved base token with 6 decimals (OPC Fee = 0.2%)\n    \"\"\"\n    bt_deployer_wallet = factory_deployer_wallet\n    consume_market_swap_fee_collector = get_wallet(7)\n\n    router = FactoryRouter(config, get_address_of_type(config, \"Router\"))\n    FRE = FixedRateExchange(config, get_address_of_type(config, \"FixedPrice\"))\n\n    bt = Datatoken1(config, get_address_of_type(config, bt_name))\n    dt = DT\n\n    transfer_bt_if_balance_lte(\n        config=config,\n        bt_address=bt.address,\n        from_wallet=bt_deployer_wallet,\n        recipient=alice.address,\n        min_balance=int_units(\"1500\", bt.decimals()),\n        amount_to_transfer=int_units(\"1500\", bt.decimals()),\n    )\n\n    transfer_bt_if_balance_lte(\n        config=config,\n        bt_address=bt.address,\n        from_wallet=bt_deployer_wallet,\n        recipient=bob.address,\n        min_balance=int_units(\"1500\", bt.decimals()),\n        amount_to_transfer=int_units(\"1500\", bt.decimals()),\n    )\n\n    publish_market_swap_fee = to_wei(publish_market_swap_fee)\n    consume_market_swap_fee = to_wei(consume_market_swap_fee)\n\n    bt_per_dt_in_wei = to_wei(bt_per_dt)\n    exchange = dt.create_exchange(\n        rate=bt_per_dt_in_wei,\n        base_token_addr=bt.address,\n        tx_dict={\"from\": alice},\n        owner_addr=alice.address,\n        publish_market_fee_collector=alice.address,\n        publish_market_fee=publish_market_swap_fee,\n        allowed_swapper=ZERO_ADDRESS,\n    )\n\n    fees = exchange.exchange_fees_info\n\n    # Verify fee collectors are configured correctly\n    assert fees.publish_market_fee_collector == alice.address\n\n    # Verify fees are configured correctly\n    if router.isApprovedToken(bt.address):\n        assert fees.opc_fee == OPC_SWAP_FEE_APPROVED\n    else:\n        assert fees.opc_fee == OPC_SWAP_FEE_NOT_APPROVED\n    assert FRE.getOPCFee(bt.address) == fees.opc_fee == router.getOPCFee(bt.address)\n    assert (\n        exchange.get_publish_market_fee()\n        == publish_market_swap_fee\n        == fees.publish_market_fee\n    )\n\n    # Verify 0 fees have been collected so far\n    assert fees.publish_market_fee_available == 0\n    assert fees.ocean_fee_available == 0\n\n    # Verify that rate is configured correctly\n    assert exchange.get_rate() == bt_per_dt_in_wei\n\n    # Verify exchange starting balance and supply\n    details = exchange.details\n    assert details.bt_balance == 0\n    assert details.dt_balance == 0\n    assert details.dt_supply == dt.cap()\n\n    # Grant infinite approvals for exchange to spend bob's BT and DT\n    dt.approve(exchange.address, MAX_UINT256, {\"from\": bob})\n    bt.approve(exchange.address, MAX_UINT256, {\"from\": bob})\n\n    one_base_token = int_units(\"1\", bt.decimals())\n    dt_per_bt_in_wei = to_wei(1.0 / float(bt_per_dt))\n\n    buy_or_sell_dt_and_verify_balances_swap_fees(\n        \"buy\",\n        convert_bt_amt_to_dt(one_base_token, bt.decimals(), dt_per_bt_in_wei),\n        config,\n        exchange,\n        consume_market_swap_fee_collector.address,\n        consume_market_swap_fee,\n        bob,\n    )\n\n    buy_or_sell_dt_and_verify_balances_swap_fees(\n        \"sell\",\n        convert_bt_amt_to_dt(one_base_token, bt.decimals(), dt_per_bt_in_wei),\n        config,\n        exchange,\n        consume_market_swap_fee_collector.address,\n        consume_market_swap_fee,\n        bob,\n    )\n\n    # Collect BT\n    collect_bt_or_dt_and_verify_balances(\n        bt.address,\n        config,\n        exchange,\n        bob,\n    )\n\n    # Collect DT\n    collect_bt_or_dt_and_verify_balances(\n        dt.address,\n        config,\n        exchange,\n        bob,\n    )\n\n    # Update publish market swap fee\n    new_publish_market_swap_fee = to_wei(0.09)\n    exchange.update_publish_market_fee(new_publish_market_swap_fee, {\"from\": alice})\n    assert exchange.exchange_fees_info.publish_market_fee == new_publish_market_swap_fee\n\n    # Increase rate (base tokens per datatoken) by 1\n    new_bt_per_dt_in_wei = bt_per_dt_in_wei + to_wei(1)\n    exchange.set_rate(new_bt_per_dt_in_wei, {\"from\": alice})\n    assert exchange.get_rate() == new_bt_per_dt_in_wei\n    new_dt_per_bt_in_wei = to_wei(1.0 / from_wei(new_bt_per_dt_in_wei))\n\n    buy_or_sell_dt_and_verify_balances_swap_fees(\n        \"buy\",\n        convert_bt_amt_to_dt(one_base_token, bt.decimals(), new_dt_per_bt_in_wei),\n        config,\n        exchange,\n        consume_market_swap_fee_collector.address,\n        consume_market_swap_fee,\n        bob,\n    )\n\n    # Make Bob the new market fee collector\n    exchange.update_publish_market_fee_collector(bob.address, {\"from\": alice})\n\n    # Collect publish market fee\n    collect_fee_and_verify_balances(\n        \"publish_market_fee\",\n        config,\n        exchange,\n        bob,\n    )\n\n    # Collect ocean fee\n    collect_fee_and_verify_balances(\n        \"ocean_fee\",\n        config,\n        exchange,\n        bob,\n    )\n\n\ndef buy_or_sell_dt_and_verify_balances_swap_fees(\n    action: str,  # \"buy\" or \"sell\"\n    dt_amount: int,\n    config: dict,\n    exchange: OneExchange,\n    consume_market_swap_fee_address: str,\n    consume_market_swap_fee: int,\n    bob,\n):\n    details = exchange.details\n    bt = Datatoken1(config, details.base_token)\n    dt = Datatoken1(config, details.datatoken)\n\n    # Get balances before swap\n    BT_bob1 = bt.balanceOf(bob)\n    DT_bob1 = dt.balanceOf(bob)\n    BT_exchange1 = details.bt_balance\n    DT_exchange1 = details.dt_balance\n\n    BT_publish_market_fee_avail1 = (\n        exchange.exchange_fees_info.publish_market_fee_available\n    )\n    BT_opc_fee_avail1 = exchange.exchange_fees_info.ocean_fee_available\n    BT_consume_market_fee_avail1 = bt.balanceOf(consume_market_swap_fee_address)\n\n    if action == \"buy\":\n        method = exchange.buy_DT\n        min_or_max_bt = MAX_UINT256\n    elif action == \"sell\":\n        method = exchange.sell_DT\n        min_or_max_bt = 0\n    else:\n        raise ValueError(action)\n\n    # buy_DT() or sell_DT()\n    tx = method(\n        dt_amount,\n        {\"from\": bob},\n        min_or_max_bt,\n        consume_market_swap_fee_address,\n        consume_market_swap_fee,\n    )\n\n    # Get balances after swap\n    details = exchange.details\n    BT_bob2 = bt.balanceOf(bob)\n    DT_bob2 = dt.balanceOf(bob)\n    BT_exchange2 = details.bt_balance\n    DT_exchange2 = details.dt_balance\n\n    # Get Swapped event\n    swapped_event = exchange._FRE.contract.events.Swapped().process_receipt(\n        tx, errors=DISCARD\n    )[0]\n    BT_publish_market_fee_amt = swapped_event.args.marketFeeAmount\n    BT_consume_market_fee_amt = swapped_event.args.consumeMarketFeeAmount\n    BT_opc_fee_amt = swapped_event.args.oceanFeeAmount\n\n    if swapped_event.args.tokenOutAddress == dt.address:\n        BT_amt_swapped = swapped_event.args.baseTokenSwappedAmount\n        DT_amt_swapped = swapped_event.args.datatokenSwappedAmount\n        assert (BT_bob1 - BT_amt_swapped) == BT_bob2\n        assert (DT_bob1 + DT_amt_swapped) == DT_bob2\n\n        assert (\n            BT_exchange1\n            + BT_amt_swapped\n            - BT_publish_market_fee_amt\n            - BT_opc_fee_amt\n            - BT_consume_market_fee_amt\n            == BT_exchange2\n        )\n        # When buying DT, exchange DT bal doesn't change bc exchange *mints* DT\n        assert DT_exchange1 == DT_exchange2\n\n    elif swapped_event.args.tokenOutAddress == bt.address:\n        DT_amt_swapped = swapped_event.args.datatokenSwappedAmount\n        BT_amt_swapped = swapped_event.args.baseTokenSwappedAmount\n        assert (DT_bob1 - DT_amt_swapped) == DT_bob2\n        assert (BT_bob1 + BT_amt_swapped) == BT_bob2\n\n        assert (\n            BT_exchange1\n            - BT_amt_swapped\n            - BT_publish_market_fee_amt\n            - BT_opc_fee_amt\n            - BT_consume_market_fee_amt\n            == BT_exchange2\n        )\n        assert DT_exchange1 + DT_amt_swapped == DT_exchange2\n\n    else:\n        raise ValueError(swapped_event[\"tokenOutAddress\"])\n\n    # Get current fee balances\n    # Exchange fees are always base tokens\n    BT_publish_market_fee_avail2 = (\n        exchange.exchange_fees_info.publish_market_fee_available\n    )\n    BT_opc_fee_avail2 = exchange.exchange_fees_info.ocean_fee_available\n    BT_consume_market_fee_avail2 = bt.balanceOf(consume_market_swap_fee_address)\n\n    # Check fees\n    assert (\n        BT_publish_market_fee_avail1 + BT_publish_market_fee_amt\n        == BT_publish_market_fee_avail2\n    )\n    assert (\n        BT_consume_market_fee_avail1 + BT_consume_market_fee_amt\n        == BT_consume_market_fee_avail2\n    )\n    assert BT_opc_fee_avail1 + BT_opc_fee_amt == BT_opc_fee_avail2\n\n\ndef collect_bt_or_dt_and_verify_balances(\n    token_address: str,\n    config: dict,\n    exchange: OneExchange,\n    from_wallet,\n):\n    \"\"\"Collet BT or Collect DT and verify balances\"\"\"\n    details = exchange.details\n    dt = Datatoken1(config, details.datatoken)\n    bt = Datatoken1(config, details.base_token)\n    publish_market = dt.getPaymentCollector()\n\n    if token_address == dt.address:\n        exchange_token_bal1 = details.dt_balance\n        token = dt\n        balance_index = \"dt_balance\"\n        method = exchange.collect_DT\n    else:\n        exchange_token_bal1 = details.bt_balance\n        token = bt\n        balance_index = \"bt_balance\"\n        method = exchange.collect_BT\n\n    publish_market_token_bal1 = token.balanceOf(publish_market)\n\n    method(exchange_token_bal1, {\"from\": from_wallet})\n\n    details = exchange.details\n    if balance_index == \"dt_balance\":\n        exchange_token_bal2 = details.dt_balance\n    else:\n        exchange_token_bal2 = details.bt_balance\n\n    publish_market_token_bal2 = token.balanceOf(publish_market)\n\n    assert exchange_token_bal2 == 0\n    assert publish_market_token_bal1 + exchange_token_bal1 == publish_market_token_bal2\n\n\ndef collect_fee_and_verify_balances(\n    fee_type: str,\n    config: dict,\n    exchange: OneExchange,\n    from_wallet,\n):\n    \"\"\"Collect publish_market  or opc fees, and verify balances\"\"\"\n    FRE = FixedRateExchange(config, get_address_of_type(config, \"FixedPrice\"))\n    bt = Datatoken1(config, exchange.details.base_token)\n\n    if fee_type == \"publish_market_fee\":\n        BT_exchange_fee_avail1 = (\n            exchange.exchange_fees_info.publish_market_fee_available\n        )\n        method = exchange.collect_publish_market_fee\n        fee_collector = exchange.exchange_fees_info.publish_market_fee_collector\n    elif fee_type == \"ocean_fee\":\n        BT_exchange_fee_avail1 = exchange.exchange_fees_info.ocean_fee_available\n        method = exchange.collect_opc_fee\n        fee_collector = FRE.get_opc_collector()\n    else:\n        raise ValueError(fee_type)\n\n    BT_fee_collector1 = bt.balanceOf(fee_collector)\n\n    method({\"from\": from_wallet})\n\n    if fee_type == \"publish_market_fee\":\n        BT_exchange_fee_avail2 = (\n            exchange.exchange_fees_info.publish_market_fee_available\n        )\n    else:\n        BT_exchange_fee_avail2 = exchange.exchange_fees_info.ocean_fee_available\n\n    BT_fee_collector2 = bt.balanceOf(fee_collector)\n\n    assert BT_exchange_fee_avail2 == 0\n    assert (BT_fee_collector1 + BT_exchange_fee_avail1) == BT_fee_collector2\n"
  },
  {
    "path": "ocean_lib/models/test/test_exchange_main.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport time\n\nimport pytest\nfrom web3.logs import DISCARD\n\nfrom ocean_lib.models.fixed_rate_exchange import (\n    BtNeeded,\n    BtReceived,\n    ExchangeDetails,\n    ExchangeFeeInfo,\n)\nfrom ocean_lib.models.test.test_factory_router import OPC_SWAP_FEE_APPROVED\nfrom ocean_lib.ocean.util import from_wei, to_wei\nfrom ocean_lib.web3_internal.constants import MAX_UINT256, ZERO_ADDRESS\n\n\n@pytest.mark.unit\ndef test_with_defaults(OCEAN, DT, alice, bob):\n    # =========================================================================\n    # Create exchange\n    exchange = DT.create_exchange(\n        rate=to_wei(3),\n        base_token_addr=OCEAN.address,\n        tx_dict={\"from\": alice},\n    )\n\n    # Alice makes 100 datatokens available on the exchange\n    DT.mint(alice.address, to_wei(100), {\"from\": alice})\n    DT.approve(exchange.address, to_wei(100), {\"from\": alice})\n\n    # Bob lets exchange pull the OCEAN needed\n    OCEAN.approve(exchange.address, MAX_UINT256, {\"from\": bob})\n\n    details = exchange.details\n    initial_dt_supply = from_wei(details.dt_supply)\n\n    # Bob buys 2 datatokens\n    DT_bob1 = DT.balanceOf(bob)\n    _ = exchange.buy_DT(datatoken_amt=to_wei(2), tx_dict={\"from\": bob})\n    assert from_wei(DT.balanceOf(bob)) == from_wei(DT_bob1) + 2\n\n    # all exchanges for this DT\n    exchanges = DT.get_exchanges()\n    assert len(exchanges) == 1\n    assert exchanges[0].exchange_id == exchange.exchange_id\n\n    # Test details\n    details = exchange.details\n    assert details.owner == alice.address\n    assert details.datatoken == DT.address\n    assert details.dt_decimals == DT.decimals()\n    assert from_wei(details.fixed_rate) == 3\n    assert details.active\n\n    assert from_wei(details.dt_supply) == initial_dt_supply - 2\n    assert from_wei(details.bt_supply) == 2 * 3\n    assert from_wei(details.dt_balance) == 0\n    assert from_wei(details.bt_balance) == 2 * 3\n\n    # Fees tests\n    fees = exchange.exchange_fees_info\n    assert from_wei(fees.publish_market_fee) == 0  # publish mkt swap fee\n    assert fees.publish_market_fee_collector == alice.address  # for publish mkt swaps\n    assert (\n        from_wei(fees.opc_fee) == 0.001 == from_wei(OPC_SWAP_FEE_APPROVED)\n    )  # 0.1% *if* BT approved\n    assert from_wei(fees.publish_market_fee_available) == 0  # for publish mkt swaps\n    assert from_wei(fees.ocean_fee_available) == 2 * 3 * 0.001\n\n    FRE = exchange.FRE\n    assert FRE.get_opc_collector()[:2] == \"0x\", FRE.get_opc_collector()\n    assert from_wei(FRE.getOPCFee(ZERO_ADDRESS)) == 0.002  # 0.2% bc BT not approved\n\n    # Test other attributes\n    assert exchange.BT_needed(to_wei(1.0), 0) >= to_wei(3)\n    assert exchange.BT_received(to_wei(1.0), 0) >= to_wei(2)\n    assert from_wei(exchange.get_rate()) == 3\n    assert exchange.get_allowed_swapper() == ZERO_ADDRESS\n    assert exchange.is_active()\n\n    # ==========================================================================\n    # Bob sells DT to the exchange\n    DT_sell = to_wei(1.5)\n\n    DT_bob1 = DT.balanceOf(bob)\n    OCEAN_bob1 = OCEAN.balanceOf(bob)\n\n    DT.approve(exchange.address, DT_sell, {\"from\": bob})\n    exchange.sell_DT(DT_sell, tx_dict={\"from\": bob})\n\n    assert DT.balanceOf(bob) < DT_bob1\n    assert OCEAN.balanceOf(bob) > OCEAN_bob1\n\n    # ==========================================================================\n    # Change other stuff\n\n    # Alice changes market fee collector\n    exchange.update_publish_market_fee_collector(bob.address, {\"from\": alice})\n\n    # Test deactivating exchange\n    assert exchange.details.owner == alice.address\n    assert exchange.is_active()\n\n    exchange.toggle_active({\"from\": alice})\n    assert not exchange.is_active()\n\n    exchange.toggle_active({\"from\": alice})\n    assert exchange.is_active()\n\n    # Test setting rate\n    exchange.set_rate(to_wei(1.1), {\"from\": alice})\n    assert from_wei(exchange.get_rate()) == 1.1\n\n\n@pytest.mark.unit\ndef test_with_nondefaults(OCEAN, DT, alice, bob, carlos, dan, FRE):\n    # =================================================================\n    # Alice creates exchange. Bob's the owner, and carlos gets fees!\n    rate = to_wei(1)\n    publish_market_fee = to_wei(0.09)\n    publish_market_fee_collector = alice.address\n    consume_market_fee = to_wei(0.02)\n    consume_market_fee_addr = dan.address\n\n    n_exchanges1 = FRE.getNumberOfExchanges()\n    exchange, tx = DT.create_exchange(\n        rate=rate,\n        base_token_addr=OCEAN.address,\n        owner_addr=bob.address,\n        publish_market_fee_collector=publish_market_fee_collector,\n        publish_market_fee=publish_market_fee,\n        allowed_swapper=carlos.address,\n        full_info=True,\n        tx_dict={\"from\": alice},\n    )\n    assert tx is not None\n    assert FRE.getNumberOfExchanges() == (n_exchanges1 + 1)\n    assert len(FRE.getExchanges()) == (n_exchanges1 + 1)\n\n    # Test, focusing on difference from default\n    assert exchange.details.owner == bob.address\n    assert exchange.details.with_mint\n\n    assert exchange.exchange_fees_info.publish_market_fee == publish_market_fee\n    assert exchange.exchange_fees_info.publish_market_fee_collector == alice.address\n\n    assert exchange.get_allowed_swapper() == carlos.address\n\n    # =================================================================\n    # Alice makes 100 datatokens available on the exchange\n    DT.mint(alice.address, to_wei(100), {\"from\": alice})\n    DT.approve(exchange.address, to_wei(100), {\"from\": alice})\n\n    # ==================================================================\n    # Carlos buys DT. (Carlos spends OCEAN, Bob spends DT)\n    DT_buy = to_wei(11)\n    OCEAN_needed = exchange.BT_needed(DT_buy, consume_market_fee)\n    OCEAN.transfer(carlos.address, OCEAN_needed, {\"from\": bob})  # give carlos OCN\n\n    DT_carlos1 = DT.balanceOf(carlos)\n    OCEAN_carlos1 = OCEAN.balanceOf(carlos)\n\n    OCEAN.approve(exchange.address, OCEAN_needed, {\"from\": carlos})\n    tx = exchange.buy_DT(\n        datatoken_amt=DT_buy,\n        max_basetoken_amt=MAX_UINT256,\n        consume_market_fee_addr=consume_market_fee_addr,\n        consume_market_fee=consume_market_fee,\n        tx_dict={\"from\": carlos},\n    )\n\n    assert DT.balanceOf(carlos) == (DT_carlos1 + DT_buy)\n    assert OCEAN.balanceOf(carlos) == (OCEAN_carlos1 - OCEAN_needed)\n\n    # ==========================================================================\n    # Carlos sells DT to the exchange\n    DT_sell = to_wei(10)\n\n    DT_exchange1 = exchange.details.dt_supply\n    OCEAN_exchange1 = exchange.details.bt_supply\n\n    DT_carlos1 = DT.balanceOf(carlos)\n    OCEAN_carlos1 = OCEAN.balanceOf(carlos)\n\n    DT.approve(exchange.address, DT_sell, {\"from\": carlos})\n    exchange.sell_DT(\n        DT_sell,\n        min_basetoken_amt=0,\n        consume_market_fee_addr=consume_market_fee_addr,\n        consume_market_fee=consume_market_fee,\n        tx_dict={\"from\": carlos},\n    )\n\n    # Carlos should now have more OCEAN, and fewer DT\n    OCEAN_received = exchange.BT_received(DT_sell, consume_market_fee)\n    OCEAN_carlos2 = OCEAN.balanceOf(carlos)\n    DT_carlos2 = DT.balanceOf(carlos)\n\n    assert pytest.approx(from_wei(OCEAN_carlos2), 0.01) == (\n        from_wei(OCEAN_carlos1) + from_wei(OCEAN_received)\n    )\n    assert from_wei(DT_carlos2) == (from_wei(DT_carlos1) - from_wei(DT_sell))\n\n    # Test exchange's DT & OCEAN supply\n    details = exchange.details\n    OCEAN_to_exchange = to_wei(from_wei(details.fixed_rate) * from_wei(DT_sell))\n    assert from_wei(details.dt_supply) == from_wei(DT_exchange1) - from_wei(DT_sell)\n    assert from_wei(details.bt_supply) == from_wei(OCEAN_exchange1) - from_wei(\n        OCEAN_to_exchange\n    )\n\n    # ==========================================================================\n    # As payment collector, Alice collects DT payments & BT (OCEAN) payments\n    assert DT.getPaymentCollector() == alice.address\n\n    DT_alice1 = DT.balanceOf(alice)\n    receipt = exchange.collect_DT(details.dt_balance, {\"from\": alice})\n    event = exchange._FRE.contract.events.TokenCollected().process_receipt(\n        receipt, errors=DISCARD\n    )[0]\n    DT_received = event.args.amount\n    assert event.args.to == alice.address\n    DT_expected = DT_alice1 + DT_received\n\n    OCEAN_alice1 = OCEAN.balanceOf(alice)\n    receipt = exchange.collect_BT(details.bt_balance, {\"from\": alice})\n    event = exchange._FRE.contract.events.TokenCollected().process_receipt(\n        receipt, errors=DISCARD\n    )[0]\n    OCEAN_received = event.args.amount\n    assert event.args.to == alice.address\n    OCEAN_expected = OCEAN_alice1 + OCEAN_received\n\n    st_time = time.time()  # loop to ensure chain's updated (shouldn't need!!)\n    while (time.time() - st_time) < 5 and DT.balanceOf(alice) == DT_alice1:\n        time.sleep(0.2)\n    assert from_wei(DT.balanceOf(alice.address)) == from_wei(DT_expected)\n    assert from_wei(OCEAN.balanceOf(alice.address)) == from_wei(OCEAN_expected)\n\n    # ==========================================================================\n    # As publish market fee collector, Alice collects fees\n    fees = exchange.exchange_fees_info\n    assert fees.publish_market_fee > 0\n    assert fees.publish_market_fee_available > 0\n\n    OCEAN_alice1 = OCEAN.balanceOf(alice.address)\n    exchange.collect_publish_market_fee({\"from\": alice})\n    OCEAN_expected = OCEAN_alice1 + fees.publish_market_fee_available\n\n    st_time = time.time()  # loop to ensure chain's updated (shouldn't need!!)\n    while (time.time() - st_time) < 5 and OCEAN.balanceOf(alice) == OCEAN_alice1:\n        time.sleep(0.2)\n\n    assert from_wei(OCEAN.balanceOf(alice)) == from_wei(OCEAN_expected)\n\n\n@pytest.mark.unit\ndef test_ExchangeDetails():\n    owner = \"0xabc\"\n    datatoken = \"0xdef\"\n    dt_decimals = 18\n    base_token = \"0x123\"\n    bt_decimals = 10\n    fixed_rate = to_wei(0.01)\n    active = False\n    dt_supply = to_wei(100)\n    bt_supply = to_wei(101)\n    dt_balance = to_wei(10)\n    bt_balance = to_wei(11)\n    with_mint = True\n\n    tup = [\n        owner,\n        datatoken,\n        dt_decimals,\n        base_token,\n        bt_decimals,\n        fixed_rate,\n        active,\n        dt_supply,\n        bt_supply,\n        dt_balance,\n        bt_balance,\n        with_mint,\n    ]\n\n    details = ExchangeDetails(tup)\n\n    assert details.owner == owner\n    assert details.datatoken == datatoken\n    assert details.dt_decimals == dt_decimals\n    assert details.base_token == base_token\n    assert details.bt_decimals == bt_decimals\n    assert details.fixed_rate == fixed_rate\n    assert details.active == active\n    assert details.dt_supply == dt_supply\n    assert details.bt_supply == bt_supply\n    assert details.dt_balance == dt_balance\n    assert details.bt_balance == bt_balance\n    assert details.with_mint == with_mint\n\n    # Test str. Don't need to be thorough\n    s = str(details)\n    assert \"ExchangeDetails\" in s\n    assert f\"datatoken = {datatoken}\" in s\n    assert \"rate \" in s\n\n\n@pytest.mark.unit\ndef test_ExchangeFeeInfo():\n    mkt_fee = to_wei(0.03)\n    mkt_fee_coll = \"0xabc\"\n    opc_fee = to_wei(0.04)\n    mkt_avail = to_wei(0.5)\n    opc_avail = to_wei(0.6)\n\n    tup = [mkt_fee, mkt_fee_coll, opc_fee, mkt_avail, opc_avail]\n    fees = ExchangeFeeInfo(tup)\n\n    assert fees.publish_market_fee == mkt_fee\n    assert fees.publish_market_fee_collector == mkt_fee_coll\n    assert fees.opc_fee == opc_fee\n    assert fees.publish_market_fee_available == mkt_avail\n    assert fees.ocean_fee_available == opc_avail\n\n    # Test str. Don't need to be thorough\n    s = str(fees)\n    assert \"ExchangeFeeInfo\" in s\n    assert f\"publish_market_fee_collector = {mkt_fee_coll}\" in s\n\n\n@pytest.mark.unit\ndef test_BtNeeded():\n    a, b, c, d = 1, 2, 3, 4  # not realistic values, fyi\n    bt_needed = BtNeeded([a, b, c, d])\n    assert bt_needed.base_token_amount == a\n    assert bt_needed.ocean_fee_amount == b\n    assert bt_needed.publish_market_fee_amount == c\n    assert bt_needed.consume_market_fee_amount == d\n\n\n@pytest.mark.unit\ndef test_BtReceived():\n    a, b, c, d = 1, 2, 3, 4  # not realistic values, fyi\n    bt_recd = BtReceived([a, b, c, d])\n    assert bt_recd.base_token_amount == a\n    assert bt_recd.ocean_fee_amount == b\n    assert bt_recd.publish_market_fee_amount == c\n    assert bt_recd.consume_market_fee_amount == d\n"
  },
  {
    "path": "ocean_lib/models/test/test_factory_router.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\nfrom web3 import Web3\n\nfrom ocean_lib.models.factory_router import FactoryRouter\nfrom ocean_lib.ocean.util import get_address_of_type, to_wei\nfrom ocean_lib.web3_internal.constants import ZERO_ADDRESS\n\n# Constants copied from FactoryRouter.sol, used for testing purposes\nOPC_SWAP_FEE_APPROVED = to_wei(0.001)  # 0.1%\nOPC_SWAP_FEE_NOT_APPROVED = to_wei(0.002)  # 0.2%\nOPC_CONSUME_FEE = to_wei(0.03)  # 0.03 DT\nOPC_PROVIDER_FEE = to_wei(0)  # 0%\n\n\n# FactoryRouter methods\n@pytest.mark.unit\ndef test_router_owner(factory_router: FactoryRouter):\n    assert Web3.is_checksum_address(factory_router.routerOwner())\n\n\n@pytest.mark.unit\ndef test_swap_ocean_fee(factory_router: FactoryRouter):\n    assert factory_router.swapOceanFee() == OPC_SWAP_FEE_APPROVED\n\n\n@pytest.mark.unit\ndef test_swap_non_ocean_fee(factory_router: FactoryRouter):\n    assert factory_router.swapNonOceanFee() == OPC_SWAP_FEE_NOT_APPROVED\n\n\n@pytest.mark.unit\ndef test_is_approved_token(\n    config: dict, factory_router: FactoryRouter, ocean_address: str\n):\n    \"\"\"Tests that Ocean token has been added to the mapping\"\"\"\n    assert factory_router.isApprovedToken(ocean_address)\n    assert not (factory_router.isApprovedToken(ZERO_ADDRESS))\n\n\n@pytest.mark.unit\ndef test_is_fixed_rate_contract(config: dict, factory_router: FactoryRouter):\n    \"\"\"Tests that fixedRateExchange address is added to the mapping\"\"\"\n    assert factory_router.isFixedRateContract(get_address_of_type(config, \"FixedPrice\"))\n\n\n@pytest.mark.unit\ndef test_is_dispenser_contract(config: dict, factory_router: FactoryRouter):\n    assert factory_router.isDispenserContract(get_address_of_type(config, \"Dispenser\"))\n\n\n@pytest.mark.unit\ndef test_get_opc_fee(config: dict, factory_router: FactoryRouter, ocean_address: str):\n    assert factory_router.getOPCFee(ocean_address) == OPC_SWAP_FEE_APPROVED\n    assert factory_router.getOPCFee(ZERO_ADDRESS) == OPC_SWAP_FEE_NOT_APPROVED\n\n\n@pytest.mark.unit\ndef test_get_opc_fees(factory_router: FactoryRouter):\n    assert factory_router.getOPCFees() == [\n        OPC_SWAP_FEE_APPROVED,\n        OPC_SWAP_FEE_NOT_APPROVED,\n    ]\n\n\n@pytest.mark.unit\ndef test_get_opc_consume_fee(factory_router: FactoryRouter):\n    assert factory_router.getOPCConsumeFee() == OPC_CONSUME_FEE\n\n\n@pytest.mark.unit\ndef test_get_opc_provider_fee(factory_router: FactoryRouter):\n    assert factory_router.getOPCProviderFee() == OPC_PROVIDER_FEE\n\n\n@pytest.mark.unit\ndef test_opc_collector(config: dict, factory_router: FactoryRouter):\n    assert factory_router.getOPCCollector() == get_address_of_type(\n        config, \"OPFCommunityFeeCollector\"\n    )\n"
  },
  {
    "path": "ocean_lib/models/test/test_fake_ocean.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport os\n\nimport pytest\nfrom eth_account import Account\n\nfrom ocean_lib.ocean.mint_fake_ocean import mint_fake_OCEAN\nfrom ocean_lib.ocean.util import to_wei\n\n\n@pytest.mark.unit\ndef test_direct_call(config, consumer_wallet, factory_deployer_wallet, ocean_token):\n    bal_before = ocean_token.balanceOf(consumer_wallet.address)\n    amt_distribute = to_wei(1000)\n    ocean_token.mint(\n        consumer_wallet.address, amt_distribute, {\"from\": factory_deployer_wallet}\n    )\n    bal_after = ocean_token.balanceOf(consumer_wallet.address)\n    assert bal_after == (bal_before + amt_distribute)\n\n\n@pytest.mark.unit\ndef test_use_mint_fake_ocean(config, factory_deployer_wallet, ocean_token):\n    expected_amt_distribute = to_wei(2000)\n\n    mint_fake_OCEAN(config)\n\n    for key_label in [\"TEST_PRIVATE_KEY1\", \"TEST_PRIVATE_KEY2\", \"TEST_PRIVATE_KEY3\"]:\n        key = os.environ.get(key_label)\n        if not key:\n            continue\n\n        w = Account.from_key(private_key=key)\n        assert ocean_token.balanceOf(w.address) >= expected_amt_distribute\n"
  },
  {
    "path": "ocean_lib/models/ve/smart_wallet_checker.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\n\nclass SmartWalletChecker(ContractBase):\n    CONTRACT_NAME = \"SmartWalletChecker\"\n"
  },
  {
    "path": "ocean_lib/models/ve/test/conftest.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\nfrom conftest_ganache import *\n\n\n@pytest.fixture\ndef ocean(publisher_ocean):\n    return publisher_ocean\n\n\n@pytest.fixture\ndef ve_allocate(publisher_ocean):\n    return publisher_ocean.ve_allocate\n"
  },
  {
    "path": "ocean_lib/models/ve/test/test_smart_wallet_checker.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\n\n@pytest.mark.unit\ndef test1(ocean):\n    # df-py/util/test has thorough tests, so keep it super-simple here\n    assert ocean.smart_wallet_checker.address is not None\n"
  },
  {
    "path": "ocean_lib/models/ve/test/test_ve_allocate.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\nfrom web3.logs import DISCARD\n\nfrom tests.resources.helper_functions import get_wallet\n\n\n@pytest.mark.unit\ndef test_single_allocation(ve_allocate):\n    \"\"\"getveAllocation should return the correct allocation.\"\"\"\n    accounts = [\n        get_wallet(1),\n        get_wallet(2),\n        get_wallet(3),\n    ]\n\n    nftaddr1 = accounts[0].address\n    nftaddr2 = accounts[1].address\n    nftaddr3 = accounts[2].address\n\n    ve_allocate.setAllocation(100, nftaddr1, 1, {\"from\": accounts[0]})\n    assert ve_allocate.getveAllocation(accounts[0], nftaddr1, 1) == 100\n\n    ve_allocate.setAllocation(25, nftaddr2, 1, {\"from\": accounts[0]})\n    assert ve_allocate.getveAllocation(accounts[0], nftaddr2, 1) == 25\n\n    ve_allocate.setAllocation(50, nftaddr3, 1, {\"from\": accounts[0]})\n    assert ve_allocate.getveAllocation(accounts[0], nftaddr3, 1) == 50\n\n    ve_allocate.setAllocation(0, nftaddr2, 1, {\"from\": accounts[0]})\n    assert ve_allocate.getveAllocation(accounts[0], nftaddr2, 1) == 0\n\n\n@pytest.mark.unit\ndef test_single_events(ve_allocate):\n    \"\"\"Test emitted events.\"\"\"\n    accounts = [\n        get_wallet(1),\n        get_wallet(2),\n    ]\n\n    nftaddr1 = accounts[1].address\n    tx = ve_allocate.setAllocation(100, nftaddr1, 1, {\"from\": accounts[0]})\n    event = ve_allocate.contract.events.AllocationSet().process_receipt(\n        tx, errors=DISCARD\n    )[0]\n\n    assert event.args.sender == accounts[0].address\n    assert event.args.nft == accounts[1].address\n    assert event.args.chainId == 1\n    assert event.args.amount == 100\n\n\n@pytest.mark.unit\ndef test_batch_allocation(ve_allocate):\n    \"\"\"getveAllocation should return the correct allocation.\"\"\"\n    accounts = [\n        get_wallet(1),\n        get_wallet(2),\n    ]\n    nftaddr1 = accounts[0].address\n    nftaddr2 = accounts[1].address\n\n    ve_allocate.setBatchAllocation(\n        [50, 50], [nftaddr1, nftaddr2], [1, 1], {\"from\": accounts[0]}\n    )\n    assert ve_allocate.getveAllocation(accounts[0], nftaddr1, 1) == 50\n\n\n@pytest.mark.unit\ndef test_batch_events(ve_allocate):\n    \"\"\"Test emitted events.\"\"\"\n    accounts = [\n        get_wallet(1),\n        get_wallet(2),\n    ]\n\n    nftaddr1 = accounts[1].address\n    nftaddr2 = accounts[1].address\n    tx = ve_allocate.setBatchAllocation(\n        [25, 75], [nftaddr1, nftaddr2], [1, 1], {\"from\": accounts[0]}\n    )\n    event = ve_allocate.contract.events.AllocationSetMultiple().process_receipt(\n        tx, errors=DISCARD\n    )[0]\n\n    assert event.args.sender == accounts[0].address\n    assert event.args.nft == [nftaddr1, nftaddr2]\n    assert event.args.chainId == [1, 1]\n    assert event.args.amount == [25, 75]\n"
  },
  {
    "path": "ocean_lib/models/ve/test/test_ve_delegation.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\n\n@pytest.mark.unit\ndef test1(ocean):\n    # df-py/util/test has thorough tests, so keep it super-simple here\n    assert ocean.ve_delegation.address is not None\n"
  },
  {
    "path": "ocean_lib/models/ve/test/test_ve_fee_distributor.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\n\n@pytest.mark.unit\ndef test1(ocean, consumer_wallet):\n    # df-py/util/test/veOcean/test_estimateClaim.py has thorough tests\n    # therefore, we keep it super-simple here\n    assert ocean.ve_fee_distributor.address is not None\n\n    ocean.ve_fee_distributor.user_epoch_of(consumer_wallet)\n    ocean.ve_fee_distributor.time_cursor_of(consumer_wallet)\n\n    # this is the most important call from a user standpoint. $$ :)\n    ocean.ve_fee_distributor.claim({\"from\": consumer_wallet})\n"
  },
  {
    "path": "ocean_lib/models/ve/test/test_ve_fee_estimate.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\n\n@pytest.mark.unit\ndef test1(ocean):\n    # df-py/util/test has thorough tests, so keep it super-simple here\n    assert ocean.ve_fee_estimate.address is not None\n"
  },
  {
    "path": "ocean_lib/models/ve/test/test_ve_ocean.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport math\n\nimport pytest\n\nfrom ocean_lib.ocean.util import from_wei, send_ether, to_wei\n\nWEEK = 7 * 86400\nMAXTIME = 4 * 365 * 86400  # 4 years\n\n\n@pytest.mark.unit\ndef test_ve_ocean1(ocean, factory_deployer_wallet, ocean_token):\n    OCEAN = ocean.OCEAN_token\n    veOCEAN = ocean.veOCEAN\n\n    # inspiration from df-py/util/test/veOcean/test_lock.py\n    assert veOCEAN.symbol() == \"veOCEAN\"\n\n    OCEAN = ocean_token\n\n    web3 = ocean.config_dict[\"web3_instance\"]\n    alice_wallet = (\n        web3.eth.account.create()\n    )  # new account avoids \"withdraw old tokens first\"\n    send_ether(\n        ocean.config_dict, factory_deployer_wallet, alice_wallet.address, to_wei(1)\n    )\n\n    TA = to_wei(0.0001)\n    OCEAN.mint(alice_wallet.address, TA, {\"from\": factory_deployer_wallet})\n\n    latest_block = web3.eth.get_block(\"latest\")\n    veOCEAN.checkpoint({\"from\": factory_deployer_wallet, \"gas\": latest_block.gasLimit})\n    OCEAN.approve(veOCEAN.address, TA, {\"from\": alice_wallet})\n\n    latest_block = ocean.config_dict[\"web3_instance\"].eth.get_block(\"latest\")\n    t0 = latest_block.timestamp  # ve funcs use block.timestamp, not chain.time()\n    t1 = t0 // WEEK * WEEK + WEEK  # this is a Thursday, because Jan 1 1970 was\n    t2 = t1 + WEEK\n\n    provider = web3.provider\n    provider.make_request(\"evm_increaseTime\", [(t1 - t0)])\n\n    assert OCEAN.balanceOf(alice_wallet.address) != 0\n\n    latest_block = web3.eth.get_block(\"latest\")\n    veOCEAN.create_lock(\n        TA,\n        t2,\n        {\n            \"from\": alice_wallet,\n            \"gas\": latest_block.gasLimit,\n            \"gasPrice\": math.ceil(latest_block[\"baseFeePerGas\"] * 1.2),\n        },\n    )\n\n    assert OCEAN.balanceOf(alice_wallet.address) == 0\n\n    epoch = veOCEAN.user_point_epoch(alice_wallet)\n    assert epoch != 0\n\n    assert veOCEAN.get_last_user_slope(alice_wallet) != 0\n\n    latest_block = web3.eth.get_block(\"latest\")\n    alice_vote_power = float(\n        from_wei(veOCEAN.balanceOf(alice_wallet, latest_block.timestamp))\n    )\n    expected_vote_power = float(from_wei(TA)) * WEEK / MAXTIME\n    assert alice_vote_power == pytest.approx(expected_vote_power, TA / 20.0)\n\n    provider.make_request(\"evm_increaseTime\", [t2])\n    provider.make_request(\"evm_mine\", [])\n\n    latest_block = web3.eth.get_block(\"latest\")\n    veOCEAN.withdraw(\n        {\n            \"from\": alice_wallet,\n            \"gas\": latest_block.gasLimit,\n            \"gasPrice\": math.ceil(latest_block[\"baseFeePerGas\"] * 1.2),\n        }\n    )\n    assert OCEAN.balanceOf(alice_wallet.address) == TA\n\n    latest_block = web3.eth.get_block(\"latest\")\n    assert veOCEAN.get_last_user_slope(alice_wallet) == 0\n    assert veOCEAN.balanceOf(alice_wallet, latest_block.timestamp) == 0\n"
  },
  {
    "path": "ocean_lib/models/ve/ve_allocate.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\n\nclass VeAllocate(ContractBase):\n    CONTRACT_NAME = \"veAllocate\"\n"
  },
  {
    "path": "ocean_lib/models/ve/ve_delegation.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\n\nclass VeDelegation(ContractBase):\n    CONTRACT_NAME = \"veDelegation\"\n"
  },
  {
    "path": "ocean_lib/models/ve/ve_fee_distributor.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\n\nclass VeFeeDistributor(ContractBase):\n    CONTRACT_NAME = \"veFeeDistributor\"\n"
  },
  {
    "path": "ocean_lib/models/ve/ve_fee_estimate.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\n\nclass VeFeeEstimate(ContractBase):\n    CONTRACT_NAME = \"veFeeEstimate\"\n"
  },
  {
    "path": "ocean_lib/models/ve/ve_ocean.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom ocean_lib.web3_internal.contract_base import ContractBase\n\n\nclass VeOcean(ContractBase):\n    CONTRACT_NAME = \"veOCEAN\"\n"
  },
  {
    "path": "ocean_lib/ocean/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "ocean_lib/ocean/crypto.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\nfrom base64 import b64encode\nfrom hashlib import sha256\n\nfrom cryptography.fernet import Fernet\nfrom ecies import decrypt as asymmetric_decrypt\nfrom ecies import encrypt as asymmetric_encrypt\nfrom enforce_typing import enforce_types\nfrom eth_keys import keys\nfrom eth_utils import decode_hex\n\n\n@enforce_types\ndef calc_symkey(base_str: str) -> str:\n    \"\"\"Compute a symmetric private key that's a function of the base_str\"\"\"\n    base_b = base_str.encode(\"utf-8\")  # bytes\n    hash_b = sha256(base_b).hexdigest()\n    symkey_b = b64encode(hash_b.encode(\"ascii\"))[:43] + b\"=\"  # bytes\n    symkey = symkey_b.decode(\"ascii\")\n    return symkey\n\n\n@enforce_types\ndef sym_encrypt(value: str, symkey: str) -> str:\n    \"\"\"Symmetrically encrypt a value, e.g. ready to store in set_data()\"\"\"\n    value_b = value.encode(\"utf-8\")  # bytes\n    symkey_b = symkey.encode(\"utf-8\")  # bytes\n    value_enc_b = Fernet(symkey_b).encrypt(value_b)  # main work. bytes\n    value_enc = value_enc_b.decode(\"ascii\")  # ascii str\n    return value_enc\n\n\n@enforce_types\ndef sym_decrypt(value_enc: str, symkey: str) -> str:\n    \"\"\"Symmetrically decrypt a value, e.g. retrieved from get_data()\"\"\"\n    value_enc_b = value_enc.encode(\"utf-8\")\n    symkey_b = symkey.encode(\"utf-8\")\n    value_b = Fernet(symkey_b).decrypt(value_enc_b)  # main work\n    value = value_b.decode(\"ascii\")\n    return value\n\n\n@enforce_types\ndef calc_pubkey(privkey: str) -> str:\n    privkey_obj = keys.PrivateKey(decode_hex(privkey))\n    pubkey = str(privkey_obj.public_key)  # str\n    return pubkey\n\n\n@enforce_types\ndef asym_encrypt(value: str, pubkey: str) -> str:\n    \"\"\"Asymmetrically encrypt a value, e.g. ready to store in set_data()\"\"\"\n    value_b = value.encode(\"utf-8\")  # binary\n    value_enc_b = asymmetric_encrypt(pubkey, value_b)  # main work. binary\n    value_enc_h = value_enc_b.hex()  # hex str\n    return value_enc_h\n\n\n@enforce_types\ndef asym_decrypt(value_enc_h: str, privkey: str) -> str:\n    \"\"\"Asymmetrically decrypt a value, e.g. retrieved from get_data()\"\"\"\n    value_enc_b = decode_hex(value_enc_h)  # bytes\n    value_b = asymmetric_decrypt(privkey, value_enc_b)  # main work. bytes\n    value = value_b.decode(\"ascii\")\n    return value\n"
  },
  {
    "path": "ocean_lib/ocean/mint_fake_ocean.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport os\n\nfrom enforce_typing import enforce_types\nfrom eth_account import Account\n\nfrom ocean_lib.models.datatoken_base import DatatokenBase\nfrom ocean_lib.ocean.util import get_ocean_token_address, send_ether, to_wei\n\n\n@enforce_types\ndef mint_fake_OCEAN(config: dict) -> None:\n    \"\"\"\n    Does the following:\n    1. Mints tokens\n    2. Distributes tokens to TEST_PRIVATE_KEY1 and TEST_PRIVATE_KEY2\n    \"\"\"\n    deployer_wallet = Account.from_key(\n        private_key=os.getenv(\"FACTORY_DEPLOYER_PRIVATE_KEY\")\n    )\n\n    OCEAN_token = DatatokenBase.get_typed(\n        config, address=get_ocean_token_address(config)\n    )\n    amt_distribute = to_wei(2000)\n    OCEAN_token.mint(deployer_wallet.address, to_wei(20000), {\"from\": deployer_wallet})\n    for key_label in [\"TEST_PRIVATE_KEY1\", \"TEST_PRIVATE_KEY2\", \"TEST_PRIVATE_KEY3\"]:\n        key = os.environ.get(key_label)\n        if not key:\n            continue\n\n        w = Account.from_key(private_key=key)\n\n        if OCEAN_token.balanceOf(w.address) < amt_distribute:\n            OCEAN_token.mint(w.address, amt_distribute, {\"from\": deployer_wallet})\n\n        if config[\"web3_instance\"].eth.get_balance(w.address) < to_wei(2):\n            send_ether(config, deployer_wallet, w.address, to_wei(4))\n"
  },
  {
    "path": "ocean_lib/ocean/ocean.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n\"\"\"Ocean module.\"\"\"\nimport json\nimport logging\nfrom typing import Dict, List, Optional, Type, Union\n\nfrom enforce_typing import enforce_types\nfrom web3.datastructures import AttributeDict\n\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider\nfrom ocean_lib.example_config import config_defaults\nfrom ocean_lib.models.compute_input import ComputeInput\nfrom ocean_lib.models.data_nft import DataNFT\nfrom ocean_lib.models.data_nft_factory import DataNFTFactoryContract\nfrom ocean_lib.models.datatoken_base import DatatokenBase\nfrom ocean_lib.models.df.df_rewards import DFRewards\nfrom ocean_lib.models.df.df_strategy_v1 import DFStrategyV1\nfrom ocean_lib.models.dispenser import Dispenser\nfrom ocean_lib.models.factory_router import FactoryRouter\nfrom ocean_lib.models.fixed_rate_exchange import FixedRateExchange\nfrom ocean_lib.models.ve.smart_wallet_checker import SmartWalletChecker\nfrom ocean_lib.models.ve.ve_allocate import VeAllocate\nfrom ocean_lib.models.ve.ve_delegation import VeDelegation\nfrom ocean_lib.models.ve.ve_fee_distributor import VeFeeDistributor\nfrom ocean_lib.models.ve.ve_fee_estimate import VeFeeEstimate\nfrom ocean_lib.models.ve.ve_ocean import VeOcean\nfrom ocean_lib.ocean.ocean_assets import OceanAssets\nfrom ocean_lib.ocean.ocean_compute import OceanCompute\nfrom ocean_lib.ocean.util import get_address_of_type, get_ocean_token_address\nfrom ocean_lib.services.service import Service\nfrom ocean_lib.structures.algorithm_metadata import AlgorithmMetadata\n\nlogger = logging.getLogger(\"ocean\")\n\n\nclass Ocean:\n    \"\"\"The Ocean class is the entry point into Ocean Protocol.\"\"\"\n\n    @enforce_types\n    def __init__(self, config_dict: Dict, data_provider: Optional[Type] = None) -> None:\n        \"\"\"Initialize Ocean class.\n\n        Usage: Make a new Ocean instance\n\n        `ocean = Ocean({...})`\n\n        This class provides the main top-level functions in ocean protocol:\n        1. Publish assets metadata and associated services\n            - Each asset is assigned a unique DID and a DID Document (DDO)\n            - The DDO contains the asset's services including the metadata\n            - The DID is registered on-chain with a URL of the metadata store\n              to retrieve the DDO from\n\n            `ddo = ocean.assets.create(metadata, publisher_wallet)`\n\n        2. Discover/Search ddos via the current configured metadata store (Aquarius)\n\n            - Usage:\n            `ddos_list = ocean.assets.search('search text')`\n\n        An instance of Ocean is parameterized by a `Config` instance.\n\n        :param config_dict: variable definitions\n        :param data_provider: `DataServiceProvider` instance\n        \"\"\"\n        config_errors = {}\n        for key, value in config_defaults.items():\n            if key not in config_dict:\n                config_errors[key] = \"required\"\n                continue\n\n            if not isinstance(config_dict[key], type(value)):\n                config_errors[key] = f\"must be {type(value).__name__}\"\n\n        if \"web3_instance\" not in config_dict:\n            config_errors[\"web3_instance\"] = \"required\"\n\n        if \"NETWORK_NAME\" not in config_dict:\n            config_errors[\"NETWORK_NAME\"] = \"required\"\n\n        if config_errors:\n            raise Exception(json.dumps(config_errors))\n\n        self.config_dict = config_dict\n\n        if not data_provider:\n            data_provider = DataServiceProvider\n\n        self.assets = OceanAssets(self.config_dict, data_provider)\n        self.compute = OceanCompute(self.config_dict, data_provider)\n\n        logger.debug(\"Ocean instance initialized: \")\n\n    # ======================================================================\n    # OCEAN\n    @property\n    @enforce_types\n    def OCEAN_address(self) -> str:\n        return get_ocean_token_address(self.config)\n\n    @property\n    @enforce_types\n    def OCEAN_token(self) -> DatatokenBase:\n        return DatatokenBase.get_typed(self.config, self.OCEAN_address)\n\n    @property\n    @enforce_types\n    def OCEAN(self):  # alias for OCEAN_token\n        return self.OCEAN_token\n\n    # ======================================================================\n    # objects for singleton smart contracts\n    @property\n    @enforce_types\n    def data_nft_factory(self) -> DataNFTFactoryContract:\n        return DataNFTFactoryContract(self.config, self._addr(\"ERC721Factory\"))\n\n    @property\n    @enforce_types\n    def dispenser(self) -> Dispenser:\n        return Dispenser(self.config, self._addr(\"Dispenser\"))\n\n    @property\n    @enforce_types\n    def fixed_rate_exchange(self) -> FixedRateExchange:\n        return FixedRateExchange(self.config, self._addr(\"FixedPrice\"))\n\n    @property\n    @enforce_types\n    def factory_router(self) -> FactoryRouter:\n        return FactoryRouter(self.config, self._addr(\"Router\"))\n\n    # ======================================================================\n    # token getters\n    @enforce_types\n    def get_nft_token(self, token_address: str) -> DataNFT:\n        \"\"\"\n        :param token_address: Token contract address, str\n        :return: `DataNFT` instance\n        \"\"\"\n        return DataNFT(self.config, token_address)\n\n    @enforce_types\n    def get_datatoken(self, token_address: str) -> DatatokenBase:\n        \"\"\"\n        :param token_address: Token contract address, str\n        :return: `Datatoken1` or `Datatoken2` instance\n        \"\"\"\n        return DatatokenBase.get_typed(self.config, token_address)\n\n    # ======================================================================\n    # orders\n    @enforce_types\n    def get_user_orders(self, address: str, datatoken: str) -> List[AttributeDict]:\n        \"\"\"\n        :return: List of orders `[Order]`\n        \"\"\"\n        dt = DatatokenBase.get_typed(self.config_dict, datatoken)\n        _orders = []\n\n        for log in dt.get_start_order_logs(address):\n            a = dict(log.args.items())\n            a[\"amount\"] = int(log.args.amount)\n            a[\"address\"] = log.address\n            a[\"transactionHash\"] = log.transactionHash\n            a = AttributeDict(a.items())\n\n            _orders.append(a)\n\n        return _orders\n\n    # ======================================================================\n    # provider fees\n    @enforce_types\n    def retrieve_provider_fees(\n        self, ddo: DDO, access_service: Service, publisher_wallet\n    ) -> dict:\n        initialize_response = DataServiceProvider.initialize(\n            ddo.did, access_service, consumer_address=publisher_wallet.address\n        )\n        initialize_data = initialize_response.json()\n        provider_fees = initialize_data[\"providerFee\"]\n\n        return provider_fees\n\n    @enforce_types\n    def retrieve_provider_fees_for_compute(\n        self,\n        datasets: List[ComputeInput],\n        algorithm_data: Union[ComputeInput, AlgorithmMetadata],\n        consumer_address: str,\n        compute_environment: str,\n        valid_until: int,\n    ) -> dict:\n        initialize_compute_response = DataServiceProvider.initialize_compute(\n            [x.as_dictionary() for x in datasets],\n            algorithm_data.as_dictionary(),\n            datasets[0].service.service_endpoint,\n            consumer_address,\n            compute_environment,\n            valid_until,\n        )\n\n        return initialize_compute_response.json()\n\n    # ======================================================================\n    # DF/VE properties (alphabetical)\n    @property\n    @enforce_types\n    def df_rewards(self) -> DFRewards:\n        return DFRewards(self.config, self._addr(\"DFRewards\"))\n\n    @property\n    @enforce_types\n    def df_strategy_v1(self) -> DFStrategyV1:\n        return DFStrategyV1(self.config, self._addr(\"DFStrategyV1\"))\n\n    @property\n    @enforce_types\n    def smart_wallet_checker(self) -> SmartWalletChecker:\n        return SmartWalletChecker(self.config, self._addr(\"SmartWalletChecker\"))\n\n    @property\n    @enforce_types\n    def ve_allocate(self) -> VeAllocate:\n        return VeAllocate(self.config, self._addr(\"veAllocate\"))\n\n    @property\n    @enforce_types\n    def ve_delegation(self) -> VeDelegation:\n        return VeDelegation(self.config, self._addr(\"veDelegation\"))\n\n    @property\n    @enforce_types\n    def ve_fee_distributor(self) -> VeFeeDistributor:\n        return VeFeeDistributor(self.config, self._addr(\"veFeeDistributor\"))\n\n    @property\n    @enforce_types\n    def ve_fee_estimate(self) -> VeFeeEstimate:\n        return VeFeeEstimate(self.config, self._addr(\"veFeeEstimate\"))\n\n    @property\n    @enforce_types\n    def ve_ocean(self) -> VeOcean:\n        return VeOcean(self.config, self._addr(\"veOCEAN\"))\n\n    @property\n    @enforce_types\n    def veOCEAN(self) -> VeOcean:  # alias for ve_ocean\n        return self.ve_ocean\n\n    # ======================================================================\n    # helpers\n    @property\n    @enforce_types\n    def config(self) -> dict:  # alias for config_dict\n        return self.config_dict\n\n    @enforce_types\n    def _addr(self, type_str: str) -> str:\n        return get_address_of_type(self.config, type_str)\n\n    def wallet_balance(self, w):\n        return self.config[\"web3_instance\"].eth.get_balance(w.address)\n"
  },
  {
    "path": "ocean_lib/ocean/ocean_assets.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n\"\"\"Ocean module.\"\"\"\nimport json\nimport logging\nimport lzma\nimport os\nfrom datetime import datetime\nfrom typing import List, Optional, Tuple, Type, Union\n\nfrom enforce_typing import enforce_types\n\nfrom ocean_lib.agreements.consumable import AssetNotConsumable, ConsumableCodes\nfrom ocean_lib.agreements.service_types import ServiceTypes\nfrom ocean_lib.aquarius import Aquarius\nfrom ocean_lib.assets.asset_downloader import download_asset_files, is_consumable\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.data_provider.data_encryptor import DataEncryptor\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider\nfrom ocean_lib.exceptions import AquariusError, InsufficientBalance\nfrom ocean_lib.models.compute_input import ComputeInput\nfrom ocean_lib.models.data_nft import DataNFT, DataNFTArguments\nfrom ocean_lib.models.data_nft_factory import DataNFTFactoryContract\nfrom ocean_lib.models.datatoken_base import (\n    DatatokenArguments,\n    DatatokenBase,\n    TokenFeeInfo,\n)\nfrom ocean_lib.models.dispenser import DispenserArguments\nfrom ocean_lib.models.fixed_rate_exchange import ExchangeArguments\nfrom ocean_lib.ocean.util import (\n    create_checksum,\n    get_address_of_type,\n    get_args_object,\n    get_from_address,\n    to_wei,\n)\nfrom ocean_lib.services.service import Service\nfrom ocean_lib.structures.algorithm_metadata import AlgorithmMetadata\nfrom ocean_lib.structures.file_objects import (\n    ArweaveFile,\n    FilesType,\n    GraphqlQuery,\n    SmartContractCall,\n    UrlFile,\n)\nfrom ocean_lib.web3_internal.constants import ZERO_ADDRESS\n\nlogger = logging.getLogger(\"ocean\")\n\n\nclass AssetArguments:\n    def __init__(\n        self,\n        wait_for_aqua: bool = True,\n        dt_template_index: Optional[int] = 1,\n        pricing_schema_args: Optional[\n            Union[DispenserArguments, ExchangeArguments]\n        ] = None,\n        metadata: Optional[dict] = None,\n        with_compute: Optional[bool] = False,\n        compute_values: Optional[dict] = None,\n        credentials: Optional[dict] = None,\n    ):\n        self.wait_for_aqua = wait_for_aqua\n        self.dt_template_index = dt_template_index\n        self.pricing_schema_args = pricing_schema_args\n        self.metadata = metadata\n        self.with_compute = with_compute\n        self.compute_values = compute_values\n        self.credentials = credentials if credentials else {\"allow\": [], \"deny\": []}\n\n\nclass OceanAssets:\n    \"\"\"Ocean asset class for V4.\"\"\"\n\n    @enforce_types\n    def __init__(self, config_dict, data_provider: Type[DataServiceProvider]) -> None:\n        \"\"\"Initialises OceanAssets object.\"\"\"\n        self._config_dict = config_dict\n        self._chain_id = config_dict[\"CHAIN_ID\"]\n\n        self._metadata_cache_uri = config_dict.get(\"METADATA_CACHE_URI\")\n        self._data_provider = data_provider\n\n        downloads_path = os.path.join(os.getcwd(), \"downloads\")\n        self._downloads_path = config_dict.get(\"DOWNLOADS_PATH\", downloads_path)\n        self._aquarius = Aquarius.get_instance(self._metadata_cache_uri)\n\n        self.data_nft_factory = DataNFTFactoryContract(\n            self._config_dict, get_address_of_type(config_dict, \"ERC721Factory\")\n        )\n\n    @enforce_types\n    def validate(self, ddo: DDO) -> Tuple[bool, list]:\n        \"\"\"\n        Validate that the ddo is ok to be stored in aquarius.\n\n        :param ddo: DDO.\n        :return: (bool, list) list of errors, empty if valid\n        \"\"\"\n        # Validation by Aquarius\n        validation_result, validation_errors = self._aquarius.validate_ddo(ddo)\n        if not validation_result:\n            msg = f\"DDO has validation errors: {validation_errors}\"\n            logger.error(msg)\n            raise ValueError(msg)\n\n        return validation_result, validation_errors\n\n    @staticmethod\n    @enforce_types\n    def _encrypt_ddo(\n        ddo: DDO,\n        provider_uri: str,\n        encrypt_flag: Optional[bool] = True,\n        compress_flag: Optional[bool] = True,\n    ):\n        # Process the DDO\n        ddo_dict = ddo.as_dictionary()\n        ddo_string = json.dumps(ddo_dict, separators=(\",\", \":\"))\n        ddo_bytes = ddo_string.encode(\"utf-8\")\n        ddo_hash = create_checksum(ddo_string)\n\n        # Plain DDO\n        if not encrypt_flag and not compress_flag:\n            flags = bytes([0])\n            document = ddo_bytes\n            return document, flags, ddo_hash\n\n        # Only compression, not encrypted\n        if compress_flag and not encrypt_flag:\n            flags = bytes([1])\n            # Compress DDO\n            document = lzma.compress(ddo_bytes)\n            return document, flags, ddo_hash\n\n        # Only encryption, not compressed\n        if encrypt_flag and not compress_flag:\n            flags = bytes([2])\n            # Encrypt DDO\n            encrypt_response = DataEncryptor.encrypt(\n                objects_to_encrypt=ddo_string,\n                provider_uri=provider_uri,\n                chain_id=ddo.chain_id,\n            )\n            document = encrypt_response.text\n            return document, flags, ddo_hash\n\n        # Encrypted & compressed\n        flags = bytes([3])\n        # Compress DDO\n        compressed_document = lzma.compress(ddo_bytes)\n\n        # Encrypt DDO\n        encrypt_response = DataEncryptor.encrypt(\n            objects_to_encrypt=compressed_document,\n            provider_uri=provider_uri,\n            chain_id=ddo.chain_id,\n        )\n\n        document = encrypt_response.text\n\n        return document, flags, ddo_hash\n\n    @staticmethod\n    @enforce_types\n    def _assert_ddo_metadata(metadata: dict):\n        assert isinstance(\n            metadata, dict\n        ), f\"Expected metadata of type dict, got {type(metadata)}\"\n\n        asset_type = metadata.get(\"type\")\n\n        assert asset_type in (\n            \"dataset\",\n            \"algorithm\",\n        ), f\"Invalid/unsupported asset type {asset_type}\"\n\n        assert \"name\" in metadata, \"Must have name in metadata.\"\n\n    @enforce_types\n    def create_algo_asset(\n        self,\n        name: str,\n        url: str,\n        tx_dict: dict,\n        image: str = \"oceanprotocol/algo_dockers\",\n        tag: str = \"python-branin\",\n        checksum: str = \"sha256:8221d20c1c16491d7d56b9657ea09082c0ee4a8ab1a6621fa720da58b09580e4\",\n        *args,\n        **kwargs,\n    ) -> tuple:\n        \"\"\"Create asset of type \"algorithm\", having UrlFiles, with good defaults\"\"\"\n\n        asset_args = get_args_object(args, kwargs, AssetArguments)\n\n        if not asset_args.metadata:\n            metadata = OceanAssets.default_metadata(name, tx_dict, \"algorithm\")\n\n            metadata[\"algorithm\"] = {\n                \"language\": \"python\",\n                \"format\": \"docker-image\",\n                \"version\": \"0.1\",\n                \"container\": {\n                    \"entrypoint\": \"python $ALGO\",\n                    \"image\": image,\n                    \"tag\": tag,\n                    \"checksum\": checksum,\n                },\n            }\n\n            asset_args.metadata = metadata\n\n        files = [UrlFile(url)]\n\n        return self.create_bundled(files, tx_dict, asset_args)\n\n    @enforce_types\n    def create_url_asset(\n        self,\n        name: str,\n        url: str,\n        tx_dict: dict,\n        *args,\n        **kwargs,\n    ) -> tuple:\n        \"\"\"Create asset of type \"data\", having UrlFiles, with good defaults\"\"\"\n        asset_args = get_args_object(args, kwargs, AssetArguments)\n        if not asset_args.metadata:\n            asset_args.metadata = OceanAssets.default_metadata(name, tx_dict)\n\n        files = [UrlFile(url)]\n\n        return self.create_bundled(files, tx_dict, asset_args)\n\n    @enforce_types\n    def create_arweave_asset(\n        self, name: str, transaction_id: str, tx_dict: dict, *args, **kwargs\n    ) -> tuple:\n        \"\"\"Create asset of type \"data\", having UrlFiles, with good defaults\"\"\"\n        asset_args = get_args_object(args, kwargs, AssetArguments)\n        if not asset_args.metadata:\n            asset_args.metadata = OceanAssets.default_metadata(name, tx_dict)\n\n        files = [ArweaveFile(transaction_id)]\n\n        return self.create_bundled(files, tx_dict, asset_args)\n\n    @enforce_types\n    def create_graphql_asset(\n        self, name: str, url: str, query: str, tx_dict: dict, *args, **kwargs\n    ) -> tuple:\n        \"\"\"Create asset of type \"data\", having GraphqlQuery files, w good defaults\"\"\"\n        asset_args = get_args_object(args, kwargs, AssetArguments)\n        if not asset_args.metadata:\n            asset_args.metadata = OceanAssets.default_metadata(name, tx_dict)\n\n        files = [GraphqlQuery(url, query)]\n\n        return self.create_bundled(files, tx_dict, asset_args)\n\n    @enforce_types\n    def create_onchain_asset(\n        self,\n        name: str,\n        contract_address: str,\n        contract_abi: dict,\n        tx_dict: dict,\n        wait_for_aqua: bool = True,\n        dt_template_index: Optional[int] = 1,\n        pricing_schema_args: Optional[\n            Union[DispenserArguments, ExchangeArguments]\n        ] = None,\n        *args,\n        **kwargs,\n    ) -> tuple:\n        \"\"\"Create asset of type \"data\", having SmartContractCall files, w defaults\"\"\"\n        chain_id = self._chain_id\n        onchain_data = SmartContractCall(contract_address, chain_id, contract_abi)\n        files = [onchain_data]\n\n        asset_args = get_args_object(args, kwargs, AssetArguments)\n        if not asset_args.metadata:\n            asset_args.metadata = OceanAssets.default_metadata(name, tx_dict)\n\n        return self.create_bundled(files, tx_dict, asset_args)\n\n    @classmethod\n    @enforce_types\n    def default_metadata(cls, name: str, tx_dict: dict, type=\"dataset\") -> dict:\n        address = get_from_address(tx_dict)\n\n        date_created = datetime.now().isoformat()\n        metadata = {\n            \"created\": date_created,\n            \"updated\": date_created,\n            \"description\": name,\n            \"name\": name,\n            \"type\": type,\n            \"author\": address[:7],\n            \"license\": \"CC0: PublicDomain\",\n        }\n        return metadata\n\n    @enforce_types\n    def create_bundled(\n        self, files: List[FilesType], tx_dict: dict, asset_args: AssetArguments\n    ):\n        provider_uri = DataServiceProvider.get_url(self._config_dict)\n\n        self._assert_ddo_metadata(asset_args.metadata)\n        name = asset_args.metadata[\"name\"]\n        data_nft_args = DataNFTArguments(name, name)\n\n        if asset_args.dt_template_index == 2:\n            datatoken_args = DatatokenArguments(\n                f\"{name}: DT1\", files=files, template_index=2, cap=to_wei(100)\n            )\n        else:\n            datatoken_args = DatatokenArguments(f\"{name}: DT1\", files=files)\n\n        if not asset_args.pricing_schema_args:\n            data_nft, datatoken = self.data_nft_factory.create_with_erc20(\n                data_nft_args, datatoken_args, tx_dict\n            )\n\n        if isinstance(asset_args.pricing_schema_args, DispenserArguments):\n            data_nft, datatoken = self.data_nft_factory.create_with_erc20_and_dispenser(\n                data_nft_args, datatoken_args, asset_args.pricing_schema_args, tx_dict\n            )\n\n        if isinstance(asset_args.pricing_schema_args, ExchangeArguments):\n            (\n                data_nft,\n                datatoken,\n                _,\n            ) = self.data_nft_factory.create_with_erc20_and_fixed_rate(\n                data_nft_args, datatoken_args, asset_args.pricing_schema_args, tx_dict\n            )\n\n        ddo = DDO()\n        # Generate the did, add it to the ddo.\n        ddo.did = data_nft.calculate_did()\n        # Check if it's already registered first!\n        if self._aquarius.ddo_exists(ddo.did):\n            raise AquariusError(\n                f\"Asset id {ddo.did} is already registered to another asset.\"\n            )\n\n        ddo.chain_id = self._chain_id\n        ddo.metadata = asset_args.metadata\n        ddo.credentials = asset_args.credentials\n        ddo.nft_address = data_nft.address\n\n        access_service = datatoken.build_access_service(\n            service_id=\"0\",\n            service_endpoint=provider_uri,\n            files=files,\n        )\n        ddo.add_service(access_service)\n\n        if asset_args.with_compute or asset_args.compute_values:\n            ddo.create_compute_service(\n                \"1\",\n                provider_uri,\n                datatoken.address,\n                files,\n                asset_args.compute_values,\n            )\n\n        # Validation by Aquarius\n        _, proof = self.validate(ddo)\n        proof = (\n            proof[\"publicKey\"],\n            proof[\"v\"],\n            proof[\"r\"][0],\n            proof[\"s\"][0],\n        )\n\n        document, flags, ddo_hash = self._encrypt_ddo(ddo, provider_uri, True, True)\n\n        wallet_address = get_from_address(tx_dict)\n\n        data_nft.setMetaData(\n            0,\n            provider_uri,\n            wallet_address.encode(\"utf-8\"),\n            flags,\n            document,\n            ddo_hash,\n            [proof],\n            tx_dict,\n        )\n\n        # Fetch the ddo on chain\n        if asset_args.wait_for_aqua:\n            ddo = self._aquarius.wait_for_ddo(ddo.did)\n\n        return (data_nft, datatoken, ddo)\n\n    # Don't enforce types due to error:\n    # TypeError: Subscripted generics cannot be used with class and instance checks\n    def create(\n        self,\n        metadata: dict,\n        tx_dict: dict,\n        credentials: Optional[dict] = None,\n        data_nft_address: Optional[str] = None,\n        data_nft_args: Optional[DataNFTArguments] = None,\n        deployed_datatokens: Optional[List[DatatokenBase]] = None,\n        services: Optional[list] = None,\n        datatoken_args: Optional[List[\"DatatokenArguments\"]] = None,\n        encrypt_flag: Optional[bool] = True,\n        compress_flag: Optional[bool] = True,\n        wait_for_aqua: bool = True,\n    ) -> Optional[DDO]:\n        \"\"\"Register an asset on-chain. Asset = {data_NFT, >=0 datatokens, DDO}\n\n        Creating/deploying a DataNFT contract and in the Metadata store (Aquarius).\n\n        :param metadata: dict conforming to the Metadata accepted by Ocean Protocol.\n        :param publisher_wallet: account of the publisher registering this asset.\n        :param credentials: credentials dict necessary for the asset.\n        construct the serviceEndpoint for the `access` (download) service\n        :param data_nft_address: hex str the address of the data NFT. The new\n        asset will be associated with this data NFT address.\n        :param data_nft_args: object of DataNFTArguments type if creating a new one\n        :param deployed_datatokens: list of datatokens which are already deployed.\n        :param encrypt_flag: bool for encryption of the DDO.\n        :param compress_flag: bool for compression of the DDO.\n        :param wait_for_aqua: wait to ensure ddo's updated in aquarius?\n        :return: tuple of (data_nft, datatokens, ddo)\n        \"\"\"\n        self._assert_ddo_metadata(metadata)\n\n        provider_uri = DataServiceProvider.get_url(self._config_dict)\n\n        if not data_nft_address:\n            data_nft_args = data_nft_args or DataNFTArguments(\n                metadata[\"name\"], metadata[\"name\"]\n            )\n            data_nft = data_nft_args.deploy_contract(self._config_dict, tx_dict)\n            # register on-chain\n            if not data_nft:\n                logger.warning(\"Creating new NFT failed.\")\n                return None, None, None\n            logger.info(f\"Successfully created NFT with address {data_nft.address}.\")\n        else:\n            data_nft = DataNFT(self._config_dict, data_nft_address)\n\n        # Create DDO object\n        ddo = DDO()\n\n        # Generate the did, add it to the ddo.\n        ddo.did = data_nft.calculate_did()\n        # Check if it's already registered first!\n        if self._aquarius.ddo_exists(ddo.did):\n            raise AquariusError(\n                f\"Asset id {ddo.did} is already registered to another asset.\"\n            )\n        ddo.chain_id = self._chain_id\n        ddo.metadata = metadata\n\n        ddo.credentials = credentials if credentials else {\"allow\": [], \"deny\": []}\n\n        ddo.nft_address = data_nft.address\n        datatokens = []\n\n        if not deployed_datatokens:\n            services = []\n            for datatoken_arg in datatoken_args:\n                new_dt = datatoken_arg.create_datatoken(\n                    data_nft, tx_dict, with_services=True\n                )\n                datatokens.append(new_dt)\n\n                services.extend(datatoken_arg.services)\n\n            for service in services:\n                ddo.add_service(service)\n        else:\n            if not services:\n                logger.warning(\"services required with deployed_datatokens.\")\n                return None, None, None\n\n            datatokens = deployed_datatokens\n            dt_addresses = []\n            for datatoken in datatokens:\n                if deployed_datatokens[0].address not in data_nft.getTokensList():\n                    logger.warning(\n                        \"some deployed_datatokens don't belong to the given data nft.\"\n                    )\n                    return None, None, None\n\n                dt_addresses.append(datatoken.address)\n\n            for service in services:\n                if service.datatoken not in dt_addresses:\n                    logger.warning(\"Datatoken services mismatch.\")\n                    return None, None, None\n\n                ddo.add_service(service)\n\n        # Validation by Aquarius\n        _, proof = self.validate(ddo)\n        proof = (\n            proof[\"publicKey\"],\n            proof[\"v\"],\n            proof[\"r\"][0],\n            proof[\"s\"][0],\n        )\n\n        document, flags, ddo_hash = self._encrypt_ddo(\n            ddo, provider_uri, encrypt_flag, compress_flag\n        )\n\n        wallet_address = get_from_address(tx_dict)\n\n        data_nft.setMetaData(\n            0,\n            provider_uri,\n            wallet_address.encode(\"utf-8\"),\n            flags,\n            document,\n            ddo_hash,\n            [proof],\n            tx_dict,\n        )\n\n        # Fetch the ddo on chain\n        if wait_for_aqua:\n            ddo = self._aquarius.wait_for_ddo(ddo.did)\n\n        return (data_nft, datatokens, ddo)\n\n    @enforce_types\n    def update(\n        self,\n        ddo: DDO,\n        tx_dict: dict,\n        provider_uri: Optional[str] = None,\n        encrypt_flag: Optional[bool] = True,\n        compress_flag: Optional[bool] = True,\n    ) -> Optional[DDO]:\n        \"\"\"Update a ddo on-chain.\n\n        :param ddo - DDO to update\n        :param publisher_wallet - who published this ddo\n        :param provider_uri - URL of service provider. This will be used as base to construct the serviceEndpoint for the `access` (download) service\n        :param encrypt_flag - encrypt this DDO?\n        :param compress_flag - compress this DDO?\n        :return - the updated DDO, or None if updated ddo not found in aquarius\n        \"\"\"\n        self._assert_ddo_metadata(ddo.metadata)\n\n        if not provider_uri:\n            provider_uri = DataServiceProvider.get_url(self._config_dict)\n\n        assert ddo.nft_address, \"need nft address to update a ddo\"\n        data_nft = DataNFT(self._config_dict, ddo.nft_address)\n\n        assert ddo.chain_id == self._chain_id\n\n        for service in ddo.services:\n            service.encrypt_files(ddo.nft_address, ddo.chain_id)\n\n        # Validation by Aquarius\n        validation_result, errors_or_proof = self.validate(ddo)\n        if not validation_result:\n            msg = f\"DDO has validation errors: {errors_or_proof}\"\n            logger.error(msg)\n            raise ValueError(msg)\n\n        document, flags, ddo_hash = self._encrypt_ddo(\n            ddo, provider_uri, encrypt_flag, compress_flag\n        )\n\n        proof = (\n            errors_or_proof[\"publicKey\"],\n            errors_or_proof[\"v\"],\n            errors_or_proof[\"r\"][0],\n            errors_or_proof[\"s\"][0],\n        )\n\n        wallet_address = get_from_address(tx_dict)\n\n        tx_result = data_nft.setMetaData(\n            0,\n            provider_uri,\n            wallet_address.encode(\"utf-8\"),\n            flags,\n            document,\n            ddo_hash,\n            [proof],\n            tx_dict,\n        )\n\n        ddo = self._aquarius.wait_for_ddo_update(ddo, tx_result.transactionHash.hex())\n\n        return ddo\n\n    @enforce_types\n    def resolve(self, did: str) -> \"DDO\":\n        return self._aquarius.get_ddo(did)\n\n    @enforce_types\n    def search(self, text: str) -> list:\n        \"\"\"\n        Search for DDOs in aquarius that contain the target text string\n        :param text - target string\n        :return - List of DDOs that match with the query\n        \"\"\"\n        logger.info(f\"Search for DDOs containing text: {text}\")\n        text = text.replace(\":\", \"\\\\:\").replace(\"\\\\\\\\:\", \"\\\\:\")\n        return [\n            DDO.from_dict(ddo_dict[\"_source\"])\n            for ddo_dict in self._aquarius.query_search(\n                {\"query\": {\"query_string\": {\"query\": text}}}\n            )\n            if \"_source\" in ddo_dict\n        ]\n\n    @enforce_types\n    def query(self, query: dict) -> list:\n        \"\"\"\n        Search for DDOs in aquarius with a search query dict\n        :param query - dict with query parameters\n          More info at: https://docs.oceanprotocol.com/api-references/aquarius-rest-api\n        :return - List of DDOs that match the query.\n        \"\"\"\n        logger.info(f\"Search for DDOs matching query: {query}\")\n        return [\n            DDO.from_dict(ddo_dict[\"_source\"])\n            for ddo_dict in self._aquarius.query_search(query)\n            if \"_source\" in ddo_dict\n        ]\n\n    @enforce_types\n    def download_asset(\n        self,\n        ddo: DDO,\n        consumer_wallet,\n        destination: str,\n        order_tx_id: Union[str, bytes],\n        service: Optional[Service] = None,\n        index: Optional[int] = None,\n        userdata: Optional[dict] = None,\n    ) -> str:\n        service = service or ddo.services[0]  # fill in good default\n\n        if index is not None:\n            assert isinstance(index, int), logger.error(\"index has to be an integer.\")\n            assert index >= 0, logger.error(\"index has to be 0 or a positive integer.\")\n\n        assert (\n            service and service.type == ServiceTypes.ASSET_ACCESS\n        ), f\"Service with type {ServiceTypes.ASSET_ACCESS} is not found.\"\n\n        path: str = download_asset_files(\n            ddo, service, consumer_wallet, destination, order_tx_id, index, userdata\n        )\n        return path\n\n    @enforce_types\n    def pay_for_access_service(\n        self,\n        ddo: DDO,\n        tx_dict: dict,\n        service: Optional[Service] = None,\n        consume_market_fees: Optional[TokenFeeInfo] = None,\n        consumer_address: Optional[str] = None,\n        userdata: Optional[dict] = None,\n        consume_market_swap_fee_amount: Optional[int] = 0,\n        consume_market_swap_fee_address: Optional[str] = ZERO_ADDRESS,\n    ):\n        # fill in good defaults as needed\n        service = service or ddo.services[0]\n        wallet_address = get_from_address(tx_dict)\n        consumer_address = consumer_address or wallet_address\n\n        consumable_result = is_consumable(\n            ddo,\n            service,\n            {\"type\": \"address\", \"value\": wallet_address},\n            userdata=userdata,\n        )\n\n        if consumable_result != ConsumableCodes.OK:\n            raise AssetNotConsumable(consumable_result)\n\n        data_provider = DataServiceProvider\n\n        initialize_args = {\n            \"did\": ddo.did,\n            \"service\": service,\n            \"consumer_address\": consumer_address,\n        }\n\n        initialize_response = data_provider.initialize(**initialize_args)\n        provider_fees = initialize_response.json()[\"providerFee\"]\n\n        params = {\n            \"consumer\": consumer_address,\n            \"service_index\": ddo.get_index_of_service(service),\n            \"provider_fees\": provider_fees,\n            \"consume_market_fees\": consume_market_fees,\n            \"tx_dict\": tx_dict,\n        }\n\n        # main work...\n        dt = DatatokenBase.get_typed(self._config_dict, service.datatoken)\n        balance = dt.balanceOf(wallet_address)\n\n        if balance < to_wei(1):\n            try:\n                params[\n                    \"consume_market_swap_fee_amount\"\n                ] = consume_market_swap_fee_amount\n                params[\n                    \"consume_market_swap_fee_address\"\n                ] = consume_market_swap_fee_address\n                receipt = dt.get_from_pricing_schema_and_order(**params)\n            except Exception:\n                receipt = None\n\n            if receipt:\n                return receipt\n\n            raise InsufficientBalance(\n                f\"Your token balance {balance} {dt.symbol()} is not sufficient \"\n                f\"to execute the requested service. This service \"\n                f\"requires 1 wei.\"\n            )\n\n        receipt = dt.start_order(**params)\n\n        return receipt.transactionHash\n\n    @enforce_types\n    def pay_for_compute_service(\n        self,\n        datasets: List[ComputeInput],\n        algorithm_data: Union[ComputeInput, AlgorithmMetadata],\n        compute_environment: str,\n        valid_until: int,\n        consume_market_order_fee_address: str,\n        tx_dict: dict,\n        consumer_address: Optional[str] = None,\n    ):\n        data_provider = DataServiceProvider\n        wallet_address = get_from_address(tx_dict)\n\n        if not consumer_address:\n            consumer_address = wallet_address\n\n        initialize_response = data_provider.initialize_compute(\n            [x.as_dictionary() for x in datasets],\n            algorithm_data.as_dictionary(),\n            datasets[0].service.service_endpoint,\n            consumer_address,\n            compute_environment,\n            valid_until,\n        )\n\n        result = initialize_response.json()\n        for i, item in enumerate(result[\"datasets\"]):\n            self._start_or_reuse_order_based_on_initialize_response(\n                datasets[i],\n                item,\n                TokenFeeInfo(\n                    consume_market_order_fee_address,\n                    datasets[i].consume_market_order_fee_token,\n                    datasets[i].consume_market_order_fee_amount,\n                ),\n                tx_dict,\n                consumer_address,\n            )\n\n        if \"algorithm\" in result:\n            self._start_or_reuse_order_based_on_initialize_response(\n                algorithm_data,\n                result[\"algorithm\"],\n                TokenFeeInfo(\n                    address=consume_market_order_fee_address,\n                    token=algorithm_data.consume_market_order_fee_token,\n                    amount=algorithm_data.consume_market_order_fee_amount,\n                ),\n                tx_dict,\n                consumer_address,\n            )\n\n            return datasets, algorithm_data\n\n        return datasets, None\n\n    @enforce_types\n    def _start_or_reuse_order_based_on_initialize_response(\n        self,\n        asset_compute_input: ComputeInput,\n        item: dict,\n        consume_market_fees: TokenFeeInfo,\n        tx_dict: dict,\n        consumer_address: Optional[str] = None,\n    ):\n        provider_fees = item.get(\"providerFee\")\n        valid_order = item.get(\"validOrder\")\n\n        if valid_order and not provider_fees:\n            asset_compute_input.transfer_tx_id = valid_order\n            return\n\n        service = asset_compute_input.service\n        dt = DatatokenBase.get_typed(self._config_dict, service.datatoken)\n\n        if valid_order and provider_fees:\n            asset_compute_input.transfer_tx_id = dt.reuse_order(\n                valid_order, provider_fees=provider_fees, tx_dict=tx_dict\n            ).transactionHash.hex()\n            return\n\n        asset_compute_input.transfer_tx_id = dt.start_order(\n            consumer=consumer_address,\n            service_index=asset_compute_input.ddo.get_index_of_service(service),\n            provider_fees=provider_fees,\n            consume_market_fees=consume_market_fees,\n            tx_dict=tx_dict,\n        ).transactionHash.hex()\n"
  },
  {
    "path": "ocean_lib/ocean/ocean_compute.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport logging\nfrom typing import Any, Dict, List, Optional, Type\n\nfrom enforce_typing import enforce_types\n\nfrom ocean_lib.agreements.consumable import AssetNotConsumable, ConsumableCodes\nfrom ocean_lib.agreements.service_types import ServiceTypes\nfrom ocean_lib.aquarius import Aquarius\nfrom ocean_lib.assets.asset_downloader import is_consumable\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider\nfrom ocean_lib.models.compute_input import ComputeInput\nfrom ocean_lib.services.service import Service\nfrom ocean_lib.structures.algorithm_metadata import AlgorithmMetadata\n\nlogger = logging.getLogger(\"ocean\")\n\n\nclass OceanCompute:\n    @enforce_types\n    def __init__(\n        self, config_dict: dict, data_provider: Type[DataServiceProvider]\n    ) -> None:\n        \"\"\"Initialises OceanCompute class.\"\"\"\n        self._config_dict = config_dict\n        self._data_provider = data_provider\n\n    @enforce_types\n    def start(\n        self,\n        consumer_wallet,\n        dataset: ComputeInput,\n        compute_environment: str,\n        algorithm: Optional[ComputeInput] = None,\n        algorithm_meta: Optional[AlgorithmMetadata] = None,\n        algorithm_algocustomdata: Optional[dict] = None,\n        additional_datasets: List[ComputeInput] = [],\n    ) -> str:\n        metadata_cache_uri = self._config_dict.get(\"METADATA_CACHE_URI\")\n        ddo = Aquarius.get_instance(metadata_cache_uri).get_ddo(dataset.did)\n        service = ddo.get_service_by_id(dataset.service_id)\n        assert (\n            ServiceTypes.CLOUD_COMPUTE == service.type\n        ), \"service at serviceId is not of type compute service.\"\n\n        consumable_result = is_consumable(\n            ddo,\n            service,\n            {\"type\": \"address\", \"value\": consumer_wallet.address},\n            with_connectivity_check=True,\n        )\n        if consumable_result != ConsumableCodes.OK:\n            raise AssetNotConsumable(consumable_result)\n\n        # Start compute job\n        job_info = self._data_provider.start_compute_job(\n            dataset_compute_service=service,\n            consumer=consumer_wallet,\n            dataset=dataset,\n            compute_environment=compute_environment,\n            algorithm=algorithm,\n            algorithm_meta=algorithm_meta,\n            algorithm_custom_data=algorithm_algocustomdata,\n            input_datasets=additional_datasets,\n        )\n        return job_info[\"jobId\"]\n\n    @enforce_types\n    def status(self, ddo: DDO, service: Service, job_id: str, wallet) -> Dict[str, Any]:\n        \"\"\"\n        Gets job status.\n\n        :param ddo: DDO offering the compute service of this job\n        :param service: compute service of this job\n        :param job_id: str id of the compute job\n        :param wallet: Wallet instance\n        :return: dict the status for an existing compute job, keys are (ok, status, statusText)\n        \"\"\"\n        job_info = self._data_provider.compute_job_status(\n            ddo.did, job_id, service, wallet\n        )\n        job_info.update({\"ok\": job_info.get(\"status\") not in (31, 32, None)})\n\n        return job_info\n\n    @enforce_types\n    def result(\n        self, ddo: DDO, service: Service, job_id: str, index: int, wallet\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Gets job result.\n\n        :param ddo: DDO offering the compute service of this job\n        :param service: compute service of this job\n        :param job_id: str id of the compute job\n        :param index: compute result index\n        :param wallet: Wallet instance\n        :return: dict the results/logs urls for an existing compute job, keys are (did, urls, logs)\n        \"\"\"\n        result = self._data_provider.compute_job_result(job_id, index, service, wallet)\n\n        return result\n\n    @enforce_types\n    def compute_job_result_logs(\n        self,\n        ddo: DDO,\n        service: Service,\n        job_id: str,\n        wallet,\n        log_type=\"output\",\n    ) -> Dict[str, Any]:\n        \"\"\"\n        Gets job output if exists.\n\n        :param ddo: DDO offering the compute service of this job\n        :param service: compute service of this job\n        :param job_id: str id of the compute job\n        :param wallet: Wallet instance\n        :return: dict the results/logs urls for an existing compute job, keys are (did, urls, logs)\n        \"\"\"\n        result = self._data_provider.compute_job_result_logs(\n            ddo, job_id, service, wallet, log_type\n        )\n\n        return result\n\n    @enforce_types\n    def stop(self, ddo: DDO, service: Service, job_id: str, wallet) -> Dict[str, Any]:\n        \"\"\"\n        Attempt to stop the running compute job.\n\n        :param ddo: DDO offering the compute service of this job\n        :param job_id: str id of the compute job\n        :param wallet: Wallet instance\n        :return: dict the status for the stopped compute job, keys are (ok, status, statusText)\n        \"\"\"\n        job_info = self._data_provider.stop_compute_job(\n            ddo.did, job_id, service, wallet\n        )\n        job_info.update({\"ok\": job_info.get(\"status\") not in (31, 32, None)})\n        return job_info\n\n    @enforce_types\n    def get_c2d_environments(self, service_endpoint: str, chain_id: int) -> str:\n        return DataServiceProvider.get_c2d_environments(service_endpoint, chain_id)\n\n    @enforce_types\n    def get_free_c2d_environment(self, service_endpoint: str, chain_id) -> str:\n        environments = self.get_c2d_environments(service_endpoint, chain_id)\n        return next(env for env in environments if float(env[\"priceMin\"]) == float(0))\n"
  },
  {
    "path": "ocean_lib/ocean/test/conftest.py",
    "content": "from conftest_ganache import *\n"
  },
  {
    "path": "ocean_lib/ocean/test/test_crypto.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom enforce_typing import enforce_types\n\nfrom ocean_lib.ocean import crypto\n\n\n@enforce_types\ndef test_symkey():\n    base_str = \"foo\"\n    symkey = crypto.calc_symkey(base_str)\n    assert isinstance(symkey, str)\n    wrong_symkey = crypto.calc_symkey(\"testwrong\")\n    assert wrong_symkey != symkey, \"NOK : wrong_sym_key is the same as sym_key\"\n\n\n@enforce_types\ndef test_sym_encrypt_decrypt():\n    symkey = crypto.calc_symkey(\"1234\")\n\n    value = \"hello there\"\n    value_enc = crypto.sym_encrypt(value, symkey)\n    assert value_enc != value\n\n    value2 = crypto.sym_decrypt(value_enc, symkey)\n    assert value2 == value\n\n\n@enforce_types\ndef test_asym_encrypt_decrypt(alice):\n    privkey = alice._private_key.hex()  # str\n    pubkey = crypto.calc_pubkey(privkey)  # str\n\n    value = \"hello there\"\n    value_enc = crypto.asym_encrypt(value, pubkey)\n    assert value_enc != value\n\n    value2 = crypto.asym_decrypt(value_enc, privkey)\n    assert value2 == value\n"
  },
  {
    "path": "ocean_lib/ocean/test/test_ocean.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\nfrom ocean_lib.models.data_nft_factory import DataNFTFactoryContract\nfrom ocean_lib.models.datatoken1 import Datatoken1\nfrom ocean_lib.models.df.df_rewards import DFRewards\nfrom ocean_lib.models.df.df_strategy_v1 import DFStrategyV1\nfrom ocean_lib.models.dispenser import Dispenser\nfrom ocean_lib.models.factory_router import FactoryRouter\nfrom ocean_lib.models.fixed_rate_exchange import FixedRateExchange\nfrom ocean_lib.models.ve.smart_wallet_checker import SmartWalletChecker\nfrom ocean_lib.models.ve.ve_allocate import VeAllocate\nfrom ocean_lib.models.ve.ve_delegation import VeDelegation\nfrom ocean_lib.models.ve.ve_fee_distributor import VeFeeDistributor\nfrom ocean_lib.models.ve.ve_fee_estimate import VeFeeEstimate\nfrom ocean_lib.models.ve.ve_ocean import VeOcean\nfrom tests.resources.helper_functions import deploy_erc721_erc20\n\n\n@pytest.mark.unit\ndef test_nft_factory(config, publisher_ocean, publisher_wallet):\n    data_nft, datatoken = deploy_erc721_erc20(\n        config, publisher_wallet, publisher_wallet\n    )\n    ocean = publisher_ocean\n    assert ocean.data_nft_factory\n\n    assert ocean.get_nft_token(data_nft.address).address == data_nft.address\n    assert ocean.get_datatoken(datatoken.address).address == datatoken.address\n\n\n@pytest.mark.unit\ndef test_contract_objects(publisher_ocean):\n    ocean = publisher_ocean\n\n    assert ocean.OCEAN_address[:2] == \"0x\"\n    assert isinstance(ocean.OCEAN_token, Datatoken1)\n    assert isinstance(ocean.OCEAN, Datatoken1)\n    assert ocean.OCEAN_address == ocean.OCEAN_token.address\n    assert ocean.OCEAN_address == ocean.OCEAN.address\n\n    assert isinstance(ocean.data_nft_factory, DataNFTFactoryContract)\n    assert isinstance(ocean.dispenser, Dispenser)\n    assert isinstance(ocean.fixed_rate_exchange, FixedRateExchange)\n    assert isinstance(ocean.factory_router, FactoryRouter)\n\n    assert isinstance(ocean.df_rewards, DFRewards)\n    assert isinstance(ocean.df_strategy_v1, DFStrategyV1)\n    assert isinstance(ocean.smart_wallet_checker, SmartWalletChecker)\n    assert isinstance(ocean.ve_allocate, VeAllocate)\n    assert isinstance(ocean.ve_delegation, VeDelegation)\n    assert isinstance(ocean.ve_fee_distributor, VeFeeDistributor)\n    assert isinstance(ocean.ve_fee_estimate, VeFeeEstimate)\n    assert isinstance(ocean.ve_ocean, VeOcean)\n    assert isinstance(ocean.veOCEAN, VeOcean)\n\n    assert ocean.config == ocean.config_dict  # test alias\n"
  },
  {
    "path": "ocean_lib/ocean/test/test_ocean_assets.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport copy\nfrom datetime import datetime, timezone\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom ocean_lib.agreements.service_types import ServiceTypes\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider\nfrom ocean_lib.example_config import DEFAULT_PROVIDER_URL\nfrom ocean_lib.exceptions import AquariusError, InsufficientBalance\nfrom ocean_lib.models.data_nft_factory import DataNFTFactoryContract\nfrom ocean_lib.models.datatoken_base import DatatokenArguments, TokenFeeInfo\nfrom ocean_lib.models.dispenser import DispenserArguments\nfrom ocean_lib.models.fixed_rate_exchange import ExchangeArguments\nfrom ocean_lib.ocean.ocean_assets import OceanAssets\nfrom ocean_lib.ocean.util import get_address_of_type, to_wei\nfrom ocean_lib.services.service import Service\nfrom ocean_lib.web3_internal.utils import get_gas_fees\nfrom tests.resources.ddo_helpers import (\n    build_credentials_dict,\n    build_default_services,\n    get_default_files,\n    get_default_metadata,\n    get_first_service_by_type,\n    get_registered_asset_with_access_service,\n    get_sample_ddo,\n)\nfrom tests.resources.helper_functions import deploy_erc721_erc20\n\n\n@pytest.mark.integration\ndef test_register_asset(publisher_ocean):\n    invalid_did = \"did:op:0123456789\"\n    assert publisher_ocean.assets.resolve(invalid_did) is None\n\n\n@pytest.mark.integration\ndef test_update(publisher_ocean, publisher_wallet, config):\n    data_nft, _, ddo = get_registered_asset_with_access_service(\n        publisher_ocean, publisher_wallet\n    )\n\n    new_metadata = copy.deepcopy(ddo.metadata)\n\n    # Update metadata\n    _description = \"Updated description\"\n    new_metadata[\"description\"] = _description\n    new_metadata[\"updated\"] = datetime.now(timezone.utc).isoformat()\n    ddo.metadata = new_metadata\n\n    # Update credentials\n    _new_credentials = {\n        \"allow\": [{\"type\": \"address\", \"values\": [\"0x123\", \"0x456\"]}],\n        \"deny\": [{\"type\": \"address\", \"values\": [\"0x2222\", \"0x333\"]}],\n    }\n    ddo.credentials = _new_credentials\n\n    ddo2 = publisher_ocean.assets.update(ddo, {\"from\": publisher_wallet})\n\n    # Check metadata update\n    assert ddo2.datatokens == ddo.datatokens\n    assert len(ddo2.services) == len(ddo.services)\n    assert ddo2.services[0].as_dictionary() == ddo.services[0].as_dictionary()\n    assert ddo2.credentials == ddo.credentials\n    assert ddo2.metadata[\"description\"] == _description\n    assert ddo2.metadata[\"updated\"] == new_metadata[\"updated\"]\n\n    # Check credentials update\n    assert ddo2.credentials == _new_credentials, \"Credentials were not updated.\"\n\n    # Check flags update\n    registered_token_event = data_nft.get_logs(\n        \"MetadataUpdated\",\n        ddo2.event.get(\"block\"),\n        config[\"web3_instance\"].eth.get_block(\"latest\").number,\n    )\n\n    assert registered_token_event[0].args.get(\"flags\") == bytes([3])\n\n\n@pytest.mark.integration\ndef test_update_datatokens(publisher_ocean, publisher_wallet, config, file2):\n    _, datatoken = deploy_erc721_erc20(config, publisher_wallet, publisher_wallet)\n    _, _, ddo = get_registered_asset_with_access_service(\n        publisher_ocean, publisher_wallet\n    )\n\n    files = [file2]\n\n    # Add new existing datatoken with service\n    ddo_orig = copy.deepcopy(ddo)\n    access_service = Service(\n        service_id=\"3\",\n        service_type=ServiceTypes.ASSET_ACCESS,\n        service_endpoint=DEFAULT_PROVIDER_URL,\n        datatoken=datatoken.address,\n        files=files,\n        timeout=0,\n    )\n\n    ddo.datatokens.append(\n        {\n            \"address\": datatoken.address,\n            \"name\": datatoken.name(),\n            \"symbol\": datatoken.symbol(),\n            \"serviceId\": access_service.id,\n        }\n    )\n\n    ddo.services.append(access_service)\n\n    ddo2 = publisher_ocean.assets.update(ddo, {\"from\": publisher_wallet})\n\n    assert len(ddo2.datatokens) == len(ddo_orig.datatokens) + 1\n    assert len(ddo2.services) == len(ddo_orig.services) + 1\n    assert ddo2.datatokens[1].get(\"address\") == datatoken.address\n    assert ddo2.datatokens[0].get(\"address\") == ddo_orig.datatokens[0].get(\"address\")\n    assert ddo2.services[0].datatoken == ddo_orig.datatokens[0].get(\"address\")\n    assert ddo2.services[1].datatoken == datatoken.address\n\n    # Delete datatoken\n    ddo3 = copy.deepcopy(ddo2)\n    metadata3 = copy.deepcopy(ddo2.metadata)\n    _description = \"Test delete datatoken\"\n    metadata3[\"description\"] = _description\n    metadata3[\"updated\"] = datetime.now(timezone.utc).isoformat()\n\n    removed_dt = ddo3.datatokens.pop()\n\n    ddo3.services = [\n        service\n        for service in ddo3.services\n        if service.datatoken != removed_dt.get(\"address\")\n    ]\n\n    ddo2_prev_datatokens = ddo2.datatokens\n\n    ddo4 = publisher_ocean.assets.update(ddo3, {\"from\": publisher_wallet})\n\n    assert ddo4, \"Can't read ddo after update.\"\n    assert len(ddo4.datatokens) == 1\n    assert ddo4.datatokens[0].get(\"address\") == ddo2_prev_datatokens[0].get(\"address\")\n    assert ddo4.services[0].datatoken == ddo2_prev_datatokens[0].get(\"address\")\n\n    nft_token = publisher_ocean.get_nft_token(ddo4.nft[\"address\"])\n    bn = config[\"web3_instance\"].eth.get_block(\"latest\").number\n\n    updated_event = nft_token.get_logs(\"MetadataUpdated\", bn, bn)[0]\n    assert updated_event.args.updatedBy == publisher_wallet.address\n\n    validation_event = nft_token.get_logs(\"MetadataValidated\", bn, bn)[0]\n    assert validation_event.args.validator.startswith(\"0x\")\n    assert updated_event.transactionHash == validation_event.transactionHash\n\n\n@pytest.mark.integration\ndef test_ocean_assets_search():\n    # skipping as tested by the search-and-filter readme\n    assert True\n\n\n@pytest.mark.integration\ndef test_ocean_assets_validate(publisher_ocean):\n    ddo_dict = get_sample_ddo()\n    ddo = DDO.from_dict(ddo_dict)\n\n    assert publisher_ocean.assets.validate(\n        ddo\n    ), \"ddo should be valid, unless the schema changed\"\n\n    ddo_dict = get_sample_ddo()\n    ddo_dict[\"id\"] = \"something not conformant\"\n    ddo = DDO.from_dict(ddo_dict)\n\n    with pytest.raises(ValueError):\n        publisher_ocean.assets.validate(ddo)\n\n\n@pytest.mark.integration\ndef test_ocean_assets_algorithm():\n    # skipped because it is covered by c2d tests\n    assert True\n\n\n@pytest.mark.unit\ndef test_download_fails(publisher_ocean, publisher_wallet):\n    with patch(\"ocean_lib.ocean.ocean_assets.OceanAssets.resolve\") as mock:\n        ddo = DDO.from_dict(get_sample_ddo())\n        mock.return_value = ddo\n        with pytest.raises(AssertionError):\n            publisher_ocean.assets.download_asset(\n                ddo,\n                publisher_wallet,\n                destination=\"\",\n                order_tx_id=\"\",\n                service=ddo.services[0],\n                index=-4,\n            )\n        with pytest.raises(TypeError):\n            publisher_ocean.assets.download_asset(\n                ddo,\n                publisher_wallet,\n                destination=\"\",\n                order_tx_id=\"\",\n                service=ddo.services[0],\n                index=\"string_index\",\n            )\n\n\n@pytest.mark.integration\ndef test_create_bad_metadata(publisher_ocean, publisher_wallet):\n    metadata = {\n        \"created\": \"2020-11-15T12:27:48Z\",\n        \"updated\": \"2021-05-17T21:58:02Z\",\n        \"description\": \"Sample description\",\n        # name missing intentionally\n        \"type\": \"dataset\",\n        \"author\": \"OPF\",\n        \"license\": \"https://market.oceanprotocol.com/terms\",\n    }\n    with pytest.raises(AssertionError):\n        get_registered_asset_with_access_service(\n            publisher_ocean, publisher_wallet, metadata\n        )\n\n    metadata[\"name\"] = \"Sample asset\"\n    metadata.pop(\"type\")\n    with pytest.raises(AssertionError):\n        get_registered_asset_with_access_service(\n            publisher_ocean, publisher_wallet, metadata\n        )\n\n\n@pytest.mark.integration\ndef test_create_url_asset():\n    # skipped because this functionality is intrinsic to the basic_asset fixture\n    assert True\n\n\n@pytest.mark.integration\ndef test_plain_asset_with_one_datatoken(publisher_ocean, publisher_wallet, config):\n    data_nft_factory = DataNFTFactoryContract(\n        config, get_address_of_type(config, \"ERC721Factory\")\n    )\n\n    metadata = get_default_metadata()\n    files = get_default_files()\n\n    # Publisher deploys NFT contract\n    data_nft = data_nft_factory.create({\"from\": publisher_wallet}, \"NFT1\", \"NFTSYMBOL\")\n\n    _, _, ddo = publisher_ocean.assets.create(\n        metadata=metadata,\n        tx_dict={\"from\": publisher_wallet},\n        data_nft_address=data_nft.address,\n        datatoken_args=[DatatokenArguments(files=files)],\n    )\n    assert ddo, \"The ddo is not created.\"\n    assert ddo.nft[\"name\"] == \"NFT1\"\n    assert ddo.nft[\"symbol\"] == \"NFTSYMBOL\"\n    assert ddo.nft[\"address\"] == data_nft.address\n    assert ddo.nft[\"owner\"] == publisher_wallet.address\n    assert ddo.datatokens[0][\"name\"] == \"Datatoken 1\"\n    assert ddo.datatokens[0][\"symbol\"] == \"DT1\"\n    assert ddo.credentials == build_credentials_dict()\n\n\n@pytest.mark.integration\ndef test_plain_asset_multiple_datatokens(publisher_ocean, publisher_wallet, config):\n    data_nft_factory = DataNFTFactoryContract(\n        config, get_address_of_type(config, \"ERC721Factory\")\n    )\n\n    metadata = get_default_metadata()\n    files = get_default_files()\n\n    data_nft = data_nft_factory.create({\"from\": publisher_wallet}, \"NFT2\", \"NFT2SYMBOL\")\n\n    _, _, ddo = publisher_ocean.assets.create(\n        metadata=metadata,\n        tx_dict={\"from\": publisher_wallet},\n        data_nft_address=data_nft.address,\n        datatoken_args=[\n            DatatokenArguments(\"Datatoken 2\", \"DT2\", files=files),\n            DatatokenArguments(\"Datatoken 3\", \"DT3\", files=files),\n        ],\n    )\n    assert ddo, \"The ddo is not created.\"\n    assert ddo.nft[\"name\"] == \"NFT2\"\n    assert ddo.nft[\"symbol\"] == \"NFT2SYMBOL\"\n    assert ddo.nft[\"address\"] == data_nft.address\n    assert ddo.nft[\"owner\"] == publisher_wallet.address\n    assert ddo.datatokens[0][\"name\"] == \"Datatoken 2\"\n    assert ddo.datatokens[0][\"symbol\"] == \"DT2\"\n    assert ddo.datatokens[1][\"name\"] == \"Datatoken 3\"\n    assert ddo.datatokens[1][\"symbol\"] == \"DT3\"\n    assert len(ddo.services) == 2\n    assert len(ddo.datatokens) == 2\n    assert ddo.credentials == build_credentials_dict()\n\n    datatoken_names = []\n    for datatoken in ddo.datatokens:\n        datatoken_names.append(datatoken[\"name\"])\n    assert datatoken_names[0] == \"Datatoken 2\"\n    assert datatoken_names[1] == \"Datatoken 3\"\n\n\n@pytest.mark.integration\ndef test_plain_asset_multiple_services(publisher_ocean, publisher_wallet, config):\n    data_nft, datatoken = deploy_erc721_erc20(\n        config, publisher_wallet, publisher_wallet\n    )\n\n    metadata = get_default_metadata()\n    files = get_default_files()\n\n    access_service = Service(\n        service_id=\"0\",\n        service_type=ServiceTypes.ASSET_ACCESS,\n        service_endpoint=DEFAULT_PROVIDER_URL,\n        datatoken=datatoken.address,\n        files=files,\n        timeout=0,\n    )\n\n    # Set the compute values for compute service\n    compute_values = {\n        \"namespace\": \"ocean-compute\",\n        \"cpus\": 2,\n        \"gpus\": 4,\n        \"gpuType\": \"NVIDIA Tesla V100 GPU\",\n        \"memory\": \"128M\",\n        \"volumeSize\": \"2G\",\n        \"allowRawAlgorithm\": False,\n        \"allowNetworkAccess\": True,\n    }\n    compute_service = Service(\n        service_id=\"1\",\n        service_type=ServiceTypes.CLOUD_COMPUTE,\n        service_endpoint=DEFAULT_PROVIDER_URL,\n        datatoken=datatoken.address,\n        files=files,\n        timeout=3600,\n        compute_values=compute_values,\n    )\n\n    _, _, ddo = publisher_ocean.assets.create(\n        metadata=metadata,\n        tx_dict={\"from\": publisher_wallet},\n        services=[access_service, compute_service],\n        data_nft_address=data_nft.address,\n        deployed_datatokens=[datatoken],\n    )\n    assert ddo, \"The ddo is not created.\"\n    assert ddo.nft[\"name\"] == \"NFT\"\n    assert ddo.nft[\"symbol\"] == \"NFTSYMBOL\"\n    assert ddo.nft[\"address\"] == data_nft.address\n    assert ddo.nft[\"owner\"] == publisher_wallet.address\n    assert ddo.datatokens[0][\"name\"] == \"DT1\"\n    assert ddo.datatokens[0][\"symbol\"] == \"DT1Symbol\"\n    assert ddo.datatokens[0][\"address\"] == datatoken.address\n    assert ddo.credentials == build_credentials_dict()\n    assert ddo.services[1].compute_values == compute_values\n\n\n@pytest.mark.integration\ndef test_encrypted_asset(publisher_ocean, publisher_wallet, config):\n    data_nft, datatoken = deploy_erc721_erc20(\n        config, publisher_wallet, publisher_wallet\n    )\n    metadata = get_default_metadata()\n    services = build_default_services(config, datatoken)\n\n    _, _, ddo = publisher_ocean.assets.create(\n        metadata=metadata,\n        tx_dict={\"from\": publisher_wallet},\n        data_nft_address=data_nft.address,\n        deployed_datatokens=[datatoken],\n        services=services,\n        encrypt_flag=True,\n    )\n    assert ddo, \"The ddo is not created.\"\n    assert ddo.nft[\"name\"] == \"NFT\"\n    assert ddo.nft[\"symbol\"] == \"NFTSYMBOL\"\n    assert ddo.nft[\"address\"] == data_nft.address\n    assert ddo.nft[\"owner\"] == publisher_wallet.address\n    assert ddo.datatokens[0][\"name\"] == \"DT1\"\n    assert ddo.datatokens[0][\"symbol\"] == \"DT1Symbol\"\n    assert ddo.datatokens[0][\"address\"] == datatoken.address\n\n\n@pytest.mark.integration\ndef test_compressed_asset(publisher_ocean, publisher_wallet, config):\n    data_nft, datatoken = deploy_erc721_erc20(\n        config, publisher_wallet, publisher_wallet\n    )\n    metadata = get_default_metadata()\n    services = build_default_services(config, datatoken)\n\n    _, _, ddo = publisher_ocean.assets.create(\n        metadata=metadata,\n        tx_dict={\"from\": publisher_wallet},\n        services=services,\n        data_nft_address=data_nft.address,\n        deployed_datatokens=[datatoken],\n        compress_flag=True,\n    )\n    assert ddo, \"The ddo is not created.\"\n    assert ddo.nft[\"name\"] == \"NFT\"\n    assert ddo.nft[\"symbol\"] == \"NFTSYMBOL\"\n    assert ddo.nft[\"address\"] == data_nft.address\n    assert ddo.nft[\"owner\"] == publisher_wallet.address\n    assert ddo.datatokens[0][\"name\"] == \"DT1\"\n    assert ddo.datatokens[0][\"symbol\"] == \"DT1Symbol\"\n    assert ddo.datatokens[0][\"address\"] == datatoken.address\n\n\n@pytest.mark.integration\ndef test_compressed_and_encrypted_asset(publisher_ocean, publisher_wallet, config):\n    data_nft, datatoken = deploy_erc721_erc20(\n        config, publisher_wallet, publisher_wallet\n    )\n    metadata = get_default_metadata()\n    services = build_default_services(config, datatoken)\n\n    _, _, ddo = publisher_ocean.assets.create(\n        metadata=metadata,\n        tx_dict={\"from\": publisher_wallet},\n        services=services,\n        data_nft_address=data_nft.address,\n        deployed_datatokens=[datatoken],\n        encrypt_flag=True,\n        compress_flag=True,\n    )\n    assert ddo, \"The ddo is not created.\"\n    assert ddo.nft[\"name\"] == \"NFT\"\n    assert ddo.nft[\"symbol\"] == \"NFTSYMBOL\"\n    assert ddo.nft[\"owner\"] == publisher_wallet.address\n    assert ddo.datatokens[0][\"name\"] == \"DT1\"\n    assert ddo.datatokens[0][\"symbol\"] == \"DT1Symbol\"\n    assert ddo.datatokens[0][\"address\"] == datatoken.address\n\n\n@pytest.mark.unit\ndef test_asset_creation_errors(publisher_ocean, publisher_wallet, config):\n    data_nft, datatoken = deploy_erc721_erc20(\n        config, publisher_wallet, publisher_wallet\n    )\n    metadata = get_default_metadata()\n\n    with patch(\"ocean_lib.aquarius.aquarius.Aquarius.ddo_exists\") as mock:\n        mock.return_value = True\n        with pytest.raises(AquariusError):\n            publisher_ocean.assets.create(\n                metadata=metadata,\n                tx_dict={\"from\": publisher_wallet},\n                services=[],\n                data_nft_address=data_nft.address,\n                deployed_datatokens=[datatoken],\n                encrypt_flag=True,\n            )\n\n\n@pytest.mark.integration\ndef test_create_algo_asset(publisher_ocean, publisher_wallet):\n    ocean = publisher_ocean\n\n    name = \"Branin dataset\"\n    url = \"https://raw.githubusercontent.com/oceanprotocol/c2d-examples/main/branin_and_gpr/gpr.py\"\n    (data_nft, datatoken, ddo) = ocean.assets.create_algo_asset(\n        name, url, {\"from\": publisher_wallet}\n    )\n\n    assert ddo.nft[\"name\"] == name  # thorough testing is below, on create() directly\n    assert len(ddo.datatokens) == 1\n\n\n@pytest.mark.integration\ndef test_create_url_asset_with_gas_strategy(\n    config, publisher_wallet, consumer_wallet, consumer_ocean, OCEAN\n):\n    \"\"\"Directly models READMEs/gas-strategy-remote.md behavior\"\"\"\n    data_provider = DataServiceProvider\n    ocean_assets = OceanAssets(config, data_provider)\n\n    priority_fee, max_fee = get_gas_fees()\n\n    name = \"Branin dataset\"\n    url = \"https://raw.githubusercontent.com/trentmc/branin/main/branin.arff\"\n    tx_dict = {\n        \"from\": publisher_wallet,\n        \"maxPriorityFeePerGas\": priority_fee,\n        \"maxFeePerGas\": max_fee,\n    }\n\n    ddo = ocean_assets.create_url_asset(\n        name,\n        url,\n        tx_dict,\n        dt_template_index=1,\n        wait_for_aqua=False,\n    )\n\n    assert ddo\n\n\n@pytest.mark.integration\ndef test_create_pricing_schemas(\n    config, publisher_wallet, consumer_wallet, consumer_ocean, OCEAN\n):\n    data_provider = DataServiceProvider\n    ocean_assets = OceanAssets(config, data_provider)\n    url = \"https://raw.githubusercontent.com/trentmc/branin/main/branin.arff\"\n    ddo_set = {}\n\n    for dt_template_index in [2, 1]:\n        ddo_set[dt_template_index] = {}\n        # No pricing schema\n        ddo_set[dt_template_index][\"np\"] = ocean_assets.create_url_asset(\n            \"Data NFTs in Ocean\",\n            url,\n            {\"from\": publisher_wallet},\n            dt_template_index=dt_template_index,\n            wait_for_aqua=False,\n        )\n\n        ddo_set[dt_template_index][\"disp\"] = ocean_assets.create_url_asset(\n            \"Data NFTs in Ocean\",\n            url,\n            {\"from\": publisher_wallet},\n            dt_template_index=dt_template_index,\n            pricing_schema_args=DispenserArguments(to_wei(1), to_wei(1)),\n            wait_for_aqua=False,\n        )\n\n        ddo_set[dt_template_index][\"ex\"] = ocean_assets.create_url_asset(\n            \"Data NFTs in Ocean\",\n            url,\n            {\"from\": publisher_wallet},\n            dt_template_index=dt_template_index,\n            pricing_schema_args=ExchangeArguments(\n                rate=to_wei(3), base_token_addr=OCEAN.address, dt_decimals=18\n            ),\n        )\n\n    for dt_template_index in [2, 1]:\n        data_nft_np, dt_np, ddo_np = ddo_set[dt_template_index][\"np\"]\n        data_nft_disp, dt_disp, ddo_disp = ddo_set[dt_template_index][\"disp\"]\n        data_nft_ex, dt_ex, ddo_ex = ddo_set[dt_template_index][\"ex\"]\n\n        ddo_np = ocean_assets._aquarius.wait_for_ddo(ddo_np.did)\n        ddo_disp = ocean_assets._aquarius.wait_for_ddo(ddo_disp.did)\n        ddo_ex = ocean_assets._aquarius.wait_for_ddo(ddo_ex.did)\n\n        assert not dt_np.dispenser_status().active\n        assert dt_np.get_exchanges() == []\n\n        # pay_for_access service has insufficient balance and can't buy or dispense\n        empty_wallet = config[\"web3_instance\"].eth.account.create()\n\n        with pytest.raises(InsufficientBalance):\n            ocean_assets.pay_for_access_service(\n                ddo_np,\n                {\"from\": empty_wallet},\n                get_first_service_by_type(ddo_np, \"access\"),\n                TokenFeeInfo(address=empty_wallet.address, token=dt_np.address),\n            )\n\n        assert dt_disp.dispenser_status().active\n        assert dt_disp.get_exchanges() == []\n        # pay_for_access service has insufficient balance but dispenses automatically\n        _ = consumer_ocean.assets.pay_for_access_service(\n            ddo_disp, {\"from\": consumer_wallet}\n        )\n\n        assert not dt_ex.dispenser_status().active\n        assert len(dt_ex.get_exchanges()) == 1\n        assert dt_ex.get_exchanges()[0].details.base_token == OCEAN.address\n        # pay_for_access service has insufficient balance but buys 1 datatoken automatically from the exchange\n        _ = consumer_ocean.assets.pay_for_access_service(\n            ddo_ex, {\"from\": consumer_wallet}\n        )\n"
  },
  {
    "path": "ocean_lib/ocean/test/test_util.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\nimport pytest\nfrom web3 import Web3\n\nfrom ocean_lib.ocean import util\nfrom ocean_lib.ocean.util import (\n    from_wei,\n    get_address_of_type,\n    get_ocean_token_address,\n    str_with_wei,\n    to_wei,\n)\n\n\n@pytest.mark.unit\ndef test_get_ocean_token_address(config):\n    addresses = util.get_contracts_addresses(config)\n    assert addresses\n    assert isinstance(addresses, dict)\n    assert \"Ocean\" in addresses\n\n    address = get_ocean_token_address(config)\n    assert Web3.is_checksum_address(address), \"It is not a checksum token address.\"\n    assert address == Web3.to_checksum_address(addresses[\"Ocean\"])\n\n\n@pytest.mark.unit\ndef test_get_address_by_type(config):\n    addresses = util.get_contracts_addresses(config)\n\n    address = get_address_of_type(config, \"Ocean\")\n    assert Web3.is_checksum_address(address), \"It is not a checksum token address.\"\n    assert address == Web3.to_checksum_address(addresses[\"Ocean\"])\n\n\n@pytest.mark.unit\ndef test_get_address_of_type_failure(config):\n    with pytest.raises(KeyError):\n        get_address_of_type(config, \"\", \"non-existent-key\")\n\n\n@pytest.mark.unit\ndef test_wei():\n    assert from_wei(int(1234 * 1e18)) == 1234\n    assert from_wei(int(12.34 * 1e18)) == 12.34\n    assert from_wei(int(0.1234 * 1e18)) == 0.1234\n\n    assert to_wei(1234) == 1234 * 1e18 and type(to_wei(1234)) == int\n    assert to_wei(12.34) == 12.34 * 1e18 and type(to_wei(12.34)) == int\n    assert to_wei(0.1234) == 0.1234 * 1e18 and type(to_wei(0.1234)) == int\n\n    assert str_with_wei(int(12.34 * 1e18)) == \"12.34 (12340000000000000000 wei)\"\n"
  },
  {
    "path": "ocean_lib/ocean/util.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport hashlib\nfrom typing import Optional, Union\n\nfrom enforce_typing import enforce_types\nfrom web3.main import Web3\n\nfrom ocean_lib.web3_internal.contract_utils import get_contracts_addresses\n\nGANACHE_URL = \"http://127.0.0.1:8545\"\n\n\n@enforce_types\ndef get_address_of_type(\n    config_dict: dict, address_type: str, key: Optional[str] = None\n) -> str:\n    addresses = get_contracts_addresses(config_dict)\n    if address_type not in addresses.keys():\n        raise KeyError(f\"{address_type} address is not set in the config file\")\n    address = (\n        addresses[address_type]\n        if not isinstance(addresses[address_type], dict)\n        else addresses[address_type].get(key, addresses[address_type][\"1\"])\n    )\n    return Web3.to_checksum_address(address.lower())\n\n\n@enforce_types\ndef get_ocean_token_address(config_dict: dict) -> str:\n    \"\"\"Returns the Ocean token address for given network or web3 instance\n    Requires either network name or web3 instance.\n    \"\"\"\n    addresses = get_contracts_addresses(config_dict)\n\n    return (\n        Web3.to_checksum_address(addresses.get(\"Ocean\").lower()) if addresses else None\n    )\n\n\n@enforce_types\ndef create_checksum(text: str) -> str:\n    \"\"\"\n    :return: str\n    \"\"\"\n    return hashlib.sha256(text.encode(\"utf-8\")).hexdigest()\n\n\n@enforce_types\ndef from_wei(amt_wei: int):\n    return float(amt_wei / 1e18)\n\n\n@enforce_types\ndef to_wei(amt_eth) -> int:\n    return int(amt_eth * 1e18)\n\n\n@enforce_types\ndef str_with_wei(amt_wei: int) -> str:\n    return f\"{from_wei(amt_wei)} ({amt_wei} wei)\"\n\n\n@enforce_types\ndef get_from_address(tx_dict: dict) -> str:\n    address = tx_dict[\"from\"].address\n\n    return Web3.to_checksum_address(address.lower())\n\n\n@enforce_types\ndef get_args_object(args, kwargs, args_class):\n    args_to_use = None\n    if args and isinstance(args[0], args_class):\n        args_to_use = args[0]\n    elif kwargs:\n        for key, value in kwargs.items():\n            if isinstance(value, args_class):\n                args_to_use = value\n                break\n\n    if not args_to_use:\n        args_to_use = args_class(*args, **kwargs)\n\n    return args_to_use\n\n\n@enforce_types\ndef send_ether(\n    config, from_wallet, to_address: str, amount: Union[int, float], priority_fee=None\n):\n    if not Web3.is_checksum_address(to_address):\n        to_address = Web3.to_checksum_address(to_address)\n\n    web3 = config[\"web3_instance\"]\n    chain_id = web3.eth.chain_id\n    tx = {\n        \"from\": from_wallet.address,\n        \"to\": to_address,\n        \"value\": amount,\n        \"chainId\": chain_id,\n        \"nonce\": web3.eth.get_transaction_count(from_wallet.address),\n        \"type\": 2,\n    }\n    tx[\"gas\"] = web3.eth.estimate_gas(tx)\n\n    if not priority_fee:\n        priority_fee = web3.eth.max_priority_fee\n\n    base_fee = web3.eth.get_block(\"latest\")[\"baseFeePerGas\"]\n\n    tx[\"maxPriorityFeePerGas\"] = priority_fee\n    tx[\"maxFeePerGas\"] = base_fee * 2 + priority_fee\n\n    signed_tx = web3.eth.account.sign_transaction(tx, from_wallet._private_key)\n    tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)\n    return web3.eth.wait_for_transaction_receipt(tx_hash)\n"
  },
  {
    "path": "ocean_lib/services/consumer_parameters.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\"\"\"\n    Consumer Parameters Class for V4\n    To handle the consumerParameters key of a service in a DDO record\n\"\"\"\nimport copy\nimport logging\nfrom distutils.util import strtobool\nfrom typing import Any, Dict, List, Optional\n\nfrom enforce_typing import enforce_types\n\nlogger = logging.getLogger(__name__)\n\n\nclass ConsumerParameters:\n    def __init__(\n        self,\n        name: str,\n        type: str,\n        label: str,\n        required: bool,\n        default: str,\n        description: str,\n        options: Optional[List[str]] = None,\n    ) -> None:\n        fn_args = locals().copy()\n        for attr_name in ConsumerParameters.required_attrs():\n            setattr(self, attr_name, fn_args[attr_name])\n\n        if options is not None and not isinstance(options, list):\n            raise TypeError(\"Options should be a list\")\n\n        self.options = options\n\n    @classmethod\n    def from_dict(\n        cls, consumer_parameters_dict: Dict[str, Any]\n    ) -> \"ConsumerParameters\":\n        \"\"\"Create a ConsumerParameters object from a JSON string.\"\"\"\n        cpd = copy.deepcopy(consumer_parameters_dict)\n        missing_attributes = [\n            x for x in ConsumerParameters.required_attrs() if x not in cpd.keys()\n        ]\n\n        if missing_attributes:\n            raise TypeError(\n                \"ConsumerParameters is missing the keys \"\n                + \", \".join(missing_attributes)\n            )\n\n        required = cpd[\"required\"] if \"required\" in cpd else None\n\n        return cls(\n            cpd[\"name\"],\n            cpd[\"type\"],\n            cpd[\"label\"],\n            bool(strtobool(required)) if isinstance(required, str) else required,\n            cpd[\"default\"],\n            cpd[\"description\"],\n            cpd.pop(\"options\", None),\n        )\n\n    @enforce_types\n    def as_dictionary(self) -> Dict[str, Any]:\n        \"\"\"Return the consume parameters object as a python dictionary.\"\"\"\n\n        result = {\n            attr_name: getattr(self, attr_name)\n            for attr_name in ConsumerParameters.required_attrs()\n        }\n\n        if self.options is not None:\n            result[\"options\"] = self.options\n\n        return result\n\n    @staticmethod\n    @enforce_types\n    def required_attrs():\n        return [\n            \"name\",\n            \"type\",\n            \"label\",\n            \"required\",\n            \"default\",\n            \"description\",\n        ]\n"
  },
  {
    "path": "ocean_lib/services/service.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\"\"\"\n    Service Class for V4\n    To handle service items in a DDO record\n\"\"\"\nimport copy\nimport logging\nimport re\nfrom typing import Any, Dict, List, Optional, Union\n\nfrom enforce_typing import enforce_types\nfrom requests.models import Response\nfrom web3.main import Web3\n\nfrom ocean_lib.agreements.service_types import ServiceTypes, ServiceTypesNames\nfrom ocean_lib.data_provider.data_encryptor import DataEncryptor\nfrom ocean_lib.services.consumer_parameters import ConsumerParameters\nfrom ocean_lib.structures.file_objects import FilesType\n\nlogger = logging.getLogger(__name__)\n\n\nclass Service:\n    \"\"\"Service class to create validate service in a V4 DDO.\"\"\"\n\n    def __init__(\n        self,\n        service_id: str,\n        service_type: str,\n        service_endpoint: Optional[str],\n        datatoken: Optional[str],\n        files: Optional[Union[List[FilesType], str]],\n        timeout: Optional[int],\n        compute_values: Optional[Dict[str, Any]] = None,\n        name: Optional[str] = None,\n        description: Optional[str] = None,\n        additional_information: Optional[Dict[str, Any]] = None,\n        consumer_parameters=None,\n    ) -> None:\n        \"\"\"Initialize NFT Service instance.\"\"\"\n        self.id = service_id\n        self.type = service_type\n        self.service_endpoint = service_endpoint\n        self.datatoken = datatoken\n        self.files = files\n        self.timeout = timeout\n        self.compute_values = compute_values\n        self.name = name\n        self.description = description\n        self.additional_information = None\n        self.consumer_parameters = consumer_parameters\n\n        if consumer_parameters:\n            try:\n                self.consumer_parameters = [\n                    ConsumerParameters.from_dict(cp_dict)\n                    for cp_dict in consumer_parameters\n                ]\n            except AttributeError:\n                raise TypeError(\"ConsumerParameters should be a list of dictionaries.\")\n\n        if additional_information:\n            self.additional_information = additional_information\n\n        if not name or not description:\n            service_to_default_name = {\n                ServiceTypes.ASSET_ACCESS: ServiceTypesNames.DEFAULT_ACCESS_NAME,\n                ServiceTypes.CLOUD_COMPUTE: ServiceTypesNames.DEFAULT_COMPUTE_NAME,\n            }\n\n            if service_type in service_to_default_name:\n                self.name = service_to_default_name[service_type]\n                self.description = service_to_default_name[service_type]\n\n    @classmethod\n    def from_dict(cls, service_dict: Dict[str, Any]) -> \"Service\":\n        \"\"\"Create a service object from a JSON string.\"\"\"\n        sd = copy.deepcopy(service_dict)\n        service_type = sd.pop(\"type\", None)\n\n        if not service_type:\n            logger.error(\n                'Service definition in DDO document is missing the \"type\" key/value.'\n            )\n            raise IndexError\n\n        return cls(\n            sd.pop(\"id\", None),\n            service_type,\n            sd.pop(\"serviceEndpoint\", None),\n            sd.pop(\"datatokenAddress\", None),\n            sd.pop(\"files\", None),\n            sd.pop(\"timeout\", None),\n            sd.pop(\"compute\", None),\n            sd.pop(\"name\", None),\n            sd.pop(\"description\", None),\n            sd.pop(\"additionalInformation\", None),\n            sd.pop(\"consumerParameters\", None),\n        )\n\n    def get_trusted_algorithms(self) -> list:\n        return self.compute_values.get(\"publisherTrustedAlgorithms\", [])\n\n    def get_trusted_algorithm_publishers(self) -> list:\n        return self.compute_values.get(\"publisherTrustedAlgorithmPublishers\", [])\n\n    # Not type provided due to circular imports\n    def add_publisher_trusted_algorithm(self, algo_ddo) -> list:\n        \"\"\"\n        :return: List of trusted algos\n        \"\"\"\n        if self.type != ServiceTypes.CLOUD_COMPUTE:\n            raise AssertionError(\"Service is not compute type\")\n\n        initial_trusted_algos_v4 = self.get_trusted_algorithms()\n\n        # remove algo_did if already in the list\n        trusted_algos = [\n            ta for ta in initial_trusted_algos_v4 if ta[\"did\"] != algo_ddo.did\n        ]\n        initial_count = len(trusted_algos)\n\n        generated_trusted_algo_dict = algo_ddo.generate_trusted_algorithms()\n        trusted_algos.append(generated_trusted_algo_dict)\n\n        # update with the new list\n        self.compute_values[\"publisherTrustedAlgorithms\"] = trusted_algos\n\n        assert (\n            len(self.compute_values[\"publisherTrustedAlgorithms\"]) > initial_count\n        ), \"New trusted algorithm was not added. Failed when updating the privacy key. \"\n\n        return trusted_algos\n\n    def add_publisher_trusted_algorithm_publisher(self, publisher_address: str) -> list:\n        trusted_algo_publishers = [\n            Web3.to_checksum_address(tp)\n            for tp in self.get_trusted_algorithm_publishers()\n        ]\n        publisher_address = Web3.to_checksum_address(publisher_address)\n\n        if publisher_address in trusted_algo_publishers:\n            return trusted_algo_publishers\n\n        initial_len = len(trusted_algo_publishers)\n        trusted_algo_publishers.append(publisher_address)\n\n        # update with the new list\n        self.compute_values[\n            \"publisherTrustedAlgorithmPublishers\"\n        ] = trusted_algo_publishers\n        assert (\n            len(self.compute_values[\"publisherTrustedAlgorithmPublishers\"])\n            > initial_len\n        ), \"New trusted algorithm was not added. Failed when updating the privacy key. \"\n\n        return trusted_algo_publishers\n\n    def as_dictionary(self) -> Dict[str, Any]:\n        \"\"\"Return the service as a python dictionary.\"\"\"\n        # camelCase to snake case dict, matching the dict value to the attribute name\n        key_names = {\n            x: re.sub(\"([A-Z]+)\", r\"_\\1\", x).lower()\n            for x in [\n                \"name\",\n                \"description\",\n                \"id\",\n                \"type\",\n                \"files\",\n                \"datatokenAddress\",\n                \"serviceEndpoint\",\n                \"timeout\",\n                \"additionalInformation\",\n                \"consumerParameters\",\n            ]\n        }\n\n        key_names[\"datatokenAddress\"] = \"datatoken\"\n\n        optional_keys = [\n            \"name\",\n            \"description\",\n            \"additionalInformation\",\n            \"consumerParameters\",\n        ]\n\n        values = {}\n        if self.type == \"compute\":\n            if \"compute\" in self.compute_values:\n                values.update(self.compute_values)\n            else:\n                values[\"compute\"] = self.compute_values\n\n        for key, attr_name in key_names.items():\n            value = getattr(self, attr_name)\n\n            if isinstance(value, object) and hasattr(value, \"as_dictionary\"):\n                value = value.as_dictionary()\n            elif isinstance(value, list):\n                value = [\n                    v.as_dictionary() if hasattr(v, \"as_dictionary\") else v\n                    for v in value\n                ]\n\n            if key in optional_keys and value is None:\n                continue\n\n            values[key] = value\n\n        return values\n\n    def remove_publisher_trusted_algorithm(self, algo_did: str) -> list:\n        \"\"\"Returns a trusted algorithms list after removal.\"\"\"\n        trusted_algorithms = self.get_trusted_algorithms()\n        if not trusted_algorithms:\n            raise ValueError(\n                f\"Algorithm {algo_did} is not in trusted algorithms of this asset.\"\n            )\n        trusted_algorithms = [ta for ta in trusted_algorithms if ta[\"did\"] != algo_did]\n        trusted_algo_publishers = self.get_trusted_algorithm_publishers()\n        self.update_compute_values(\n            trusted_algorithms,\n            trusted_algo_publishers,\n            allow_network_access=True,\n            allow_raw_algorithm=False,\n        )\n        assert (\n            self.compute_values[\"publisherTrustedAlgorithms\"] == trusted_algorithms\n        ), \"New trusted algorithm was not removed. Failed when updating the list of trusted algorithms. \"\n\n        return trusted_algorithms\n\n    def remove_publisher_trusted_algorithm_publisher(\n        self, publisher_address: str\n    ) -> list:\n        \"\"\"\n        :return: List of trusted algo publishers not containing `publisher_address`.\n        \"\"\"\n        trusted_algorithm_publishers = [\n            tp.lower() for tp in self.get_trusted_algorithm_publishers()\n        ]\n        publisher_address = publisher_address.lower()\n        if not trusted_algorithm_publishers:\n            raise ValueError(\n                f\"Publisher {publisher_address} is not in trusted algorithm publishers of this asset.\"\n            )\n\n        trusted_algorithm_publishers = [\n            tp for tp in trusted_algorithm_publishers if tp != publisher_address\n        ]\n        trusted_algorithms = self.get_trusted_algorithms()\n        self.update_compute_values(\n            trusted_algorithms, trusted_algorithm_publishers, True, False\n        )\n        assert (\n            self.compute_values[\"publisherTrustedAlgorithmPublishers\"]\n            == trusted_algorithm_publishers\n        ), \"New trusted algorithm publisher was not removed. Failed when updating the list of trusted algo publishers. \"\n\n        return trusted_algorithm_publishers\n\n    def update_compute_values(\n        self,\n        trusted_algorithms: List,\n        trusted_algo_publishers: Optional[List],\n        allow_network_access: bool,\n        allow_raw_algorithm: bool,\n    ) -> None:\n        \"\"\"Set the `trusted_algorithms` on the compute service.\n\n        - An assertion is raised if this asset has no compute service\n        - Updates the compute service in place\n        - Adds the trusted algorithms under privacy.publisherTrustedAlgorithms\n\n        :param trusted_algorithms: list of dicts, each dict contain the keys\n            (\"containerSectionChecksum\", \"filesChecksum\", \"did\")\n        :param trusted_algo_publishers: list of strings, addresses of trusted publishers\n        :param allow_network_access: bool -- set to True to allow network access to all the algorithms that belong to this dataset\n        :param allow_raw_algorithm: bool -- determine whether raw algorithms (i.e. unpublished) can be run on this dataset\n        :return: None\n        :raises AssertionError if this asset has no `ServiceTypes.CLOUD_COMPUTE` service\n        \"\"\"\n        assert not trusted_algorithms or isinstance(trusted_algorithms, list)\n        assert (\n            self.type == ServiceTypes.CLOUD_COMPUTE is not None\n        ), \"this asset does not have a compute service.\"\n\n        for ta in trusted_algorithms:\n            assert isinstance(\n                ta, dict\n            ), f\"item in list of trusted_algorithms must be a dict, got {ta}\"\n            assert (\n                \"did\" in ta\n            ), f\"dict in list of trusted_algorithms is expected to have a `did` key, got {ta.keys()}.\"\n\n        if not self.compute_values:\n            self.compute_values = {}\n\n        self.compute_values[\"publisherTrustedAlgorithms\"] = trusted_algorithms\n        self.compute_values[\n            \"publisherTrustedAlgorithmPublishers\"\n        ] = trusted_algo_publishers\n        self.compute_values[\"allowNetworkAccess\"] = allow_network_access\n        self.compute_values[\"allowRawAlgorithm\"] = allow_raw_algorithm\n\n    @enforce_types\n    def encrypt_files(self, nft_address: str, chain_id: int) -> Response:\n        if self.files and isinstance(self.files, str):\n            return\n\n        files = list(map(lambda file: file.to_dict(), self.files))\n\n        encrypt_response = DataEncryptor.encrypt(\n            {\n                \"datatokenAddress\": self.datatoken,\n                \"nftAddress\": nft_address,\n                \"files\": files,\n            },\n            self.service_endpoint,\n            chain_id,\n        )\n\n        self.files = encrypt_response.content.decode(\"utf-8\")\n"
  },
  {
    "path": "ocean_lib/services/test/conftest.py",
    "content": "from conftest_ganache import *\n"
  },
  {
    "path": "ocean_lib/services/test/test_consumer_parameters.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\nfrom ocean_lib.services.consumer_parameters import ConsumerParameters\n\n\n@pytest.mark.unit\ndef test_consumer_parameters():\n    \"\"\"Tests the Consumer Parameters key/object.\"\"\"\n    cp_dict = {\n        \"name\": \"test_key\",\n        \"type\": \"string\",\n        \"label\": \"test_key_label\",\n        \"required\": True,\n        \"default\": \"value\",\n        \"description\": \"this is a test key\",\n        \"options\": [\"a\", \"b\"],\n    }\n\n    cp_object = ConsumerParameters.from_dict(cp_dict)\n    assert cp_object.as_dictionary() == cp_dict\n\n    cp_dict.pop(\"options\")\n    cp_object = ConsumerParameters.from_dict(cp_dict)\n    assert cp_object.as_dictionary() == cp_dict\n\n    cp_dict[\"required\"] = \"false\"  # explicitly false, not missing\n    cp_object = ConsumerParameters.from_dict(cp_dict)\n    assert cp_object.as_dictionary()[\"required\"] is False\n\n    cp_dict[\"options\"] = \"not an array\"\n    with pytest.raises(TypeError):\n        cp_object = ConsumerParameters.from_dict(cp_dict)\n\n    cp_dict.pop(\"options\")\n    cp_dict.pop(\"type\")\n    cp_dict.pop(\"label\")\n    with pytest.raises(TypeError, match=\"is missing the keys type, label\"):\n        cp_object = ConsumerParameters.from_dict(cp_dict)\n"
  },
  {
    "path": "ocean_lib/services/test/test_service.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom unittest.mock import Mock, patch\n\nimport pytest\nfrom requests.models import Response\n\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.services.service import Service\nfrom tests.resources.ddo_helpers import (\n    get_sample_algorithm_ddo,\n    get_sample_ddo,\n    get_sample_ddo_with_compute_service,\n)\n\n\n@pytest.mark.unit\ndef test_service():\n    \"\"\"Tests that the get_cost function for ServiceAgreement returns the correct value.\"\"\"\n    ddo_dict = get_sample_ddo()\n    service_dict = ddo_dict[\"services\"][0]\n    service_dict[\"additionalInformation\"] = {\"message\": \"Sample DDO\"}\n\n    cp_dict = {\n        \"name\": \"some_key\",\n        \"type\": \"string\",\n        \"label\": \"test_key_label\",\n        \"required\": True,\n        \"default\": \"value\",\n        \"description\": \"this is a test key\",\n    }\n\n    service_dict[\"consumerParameters\"] = [cp_dict]\n    sa = Service.from_dict(service_dict)\n\n    assert sa.id == \"1\"\n    assert sa.name == \"Download service\"\n    assert sa.type == \"access\"\n    assert sa.service_endpoint == \"http://172.15.0.4:8030\"\n    assert sa.datatoken == \"0x123\"\n    assert sa.additional_information == {\"message\": \"Sample DDO\"}\n\n    assert sa.as_dictionary() == {\n        \"id\": \"1\",\n        \"type\": \"access\",\n        \"serviceEndpoint\": \"http://172.15.0.4:8030\",\n        \"datatokenAddress\": \"0x123\",\n        \"files\": \"0x0000\",\n        \"timeout\": 0,\n        \"name\": \"Download service\",\n        \"description\": \"Download service\",\n        \"additionalInformation\": {\"message\": \"Sample DDO\"},\n        \"consumerParameters\": [cp_dict],\n    }\n\n    ddo_dict = get_sample_ddo()\n    service_dict = ddo_dict[\"services\"][0]\n    del service_dict[\"type\"]\n    with pytest.raises(IndexError):\n        Service.from_dict(service_dict)\n\n    ddo_dict = get_sample_ddo()\n    service_dict = ddo_dict[\"services\"][0]\n    service_dict[\"consumerParameters\"] = \"not a list\"\n    with pytest.raises(TypeError):\n        sa = Service.from_dict(service_dict)\n\n    service_dict[\"consumerParameters\"] = [\"not a dict\"]\n    with pytest.raises(TypeError):\n        sa = Service.from_dict(service_dict)\n\n    service_dict[\"consumerParameters\"] = True\n    with pytest.raises(TypeError):\n        sa = Service.from_dict(service_dict)\n\n\n@pytest.mark.unit\ndef test_additional_information():\n    \"\"\"Tests a complex structure of additional information key.\"\"\"\n    ddo_dict = get_sample_ddo()\n    service_dict = ddo_dict[\"services\"][0]\n    service_dict[\"additionalInformation\"] = {\n        \"message\": \"Sample DDO\",\n        \"some_list\": [\"a\", \"b\", \"c\"],\n        \"nested_dict\": {\"some_key\": \"value\"},\n    }\n    sa = Service.from_dict(service_dict)\n\n    assert sa.additional_information == {\n        \"message\": \"Sample DDO\",\n        \"some_list\": [\"a\", \"b\", \"c\"],\n        \"nested_dict\": {\"some_key\": \"value\"},\n    }\n    assert sa.additional_information[\"message\"] == \"Sample DDO\"\n    assert sa.additional_information[\"some_list\"][0] == \"a\"\n    assert sa.additional_information[\"some_list\"][1] == \"b\"\n    assert sa.additional_information[\"some_list\"][2] == \"c\"\n    assert sa.additional_information[\"nested_dict\"][\"some_key\"] == \"value\"\n\n    assert sa.as_dictionary() == {\n        \"id\": \"1\",\n        \"type\": \"access\",\n        \"serviceEndpoint\": \"http://172.15.0.4:8030\",\n        \"datatokenAddress\": \"0x123\",\n        \"files\": \"0x0000\",\n        \"timeout\": 0,\n        \"name\": \"Download service\",\n        \"description\": \"Download service\",\n        \"additionalInformation\": {\n            \"message\": \"Sample DDO\",\n            \"some_list\": [\"a\", \"b\", \"c\"],\n            \"nested_dict\": {\"some_key\": \"value\"},\n        },\n    }\n\n\n@pytest.mark.unit\ndef test_trusted_algo_functions(publisher_ocean):\n    algorithm_ddo = get_sample_algorithm_ddo(filename=\"ddo_algorithm2.json\")\n    algorithm_ddo.did = \"did:op:123\"\n    algorithm_ddo_v2 = get_sample_algorithm_ddo(filename=\"ddo_algorithm2.json\")\n    algorithm_ddo_v2.did = \"did:op:1234\"\n    algorithm_ddo_v3 = get_sample_algorithm_ddo(filename=\"ddo_algorithm2.json\")\n    algorithm_ddo_v3.did = \"did:op:3333\"\n\n    ddo_dict = get_sample_ddo_with_compute_service()\n    service_dict = ddo_dict[\"services\"][1]\n    compute_service = Service.from_dict(service_dict)\n    assert compute_service.type == \"compute\"\n\n    # remove an existing algorithm to publisher_trusted_algorithms list\n    new_publisher_trusted_algorithms = (\n        compute_service.remove_publisher_trusted_algorithm(algorithm_ddo.did)\n    )\n\n    assert (\n        new_publisher_trusted_algorithms is not None\n    ), \"Remove process of a trusted algorithm failed.\"\n    assert len(new_publisher_trusted_algorithms) == 1\n\n    # remove a trusted algorithm that does not belong to publisher_trusted_algorithms list\n    new_publisher_trusted_algorithms = (\n        compute_service.remove_publisher_trusted_algorithm(algorithm_ddo_v3.did)\n    )\n    assert len(new_publisher_trusted_algorithms) == 1\n\n\n@pytest.mark.unit\ndef test_utilitary_functions_for_trusted_algorithm_publishers(publisher_ocean):\n    \"\"\"Tests adding/removing trusted algorithms in the DDO metadata.\"\"\"\n    ddo = DDO.from_dict(get_sample_ddo_with_compute_service())\n    compute_service = ddo.services[1]\n    assert compute_service.type == \"compute\"\n\n    web3 = publisher_ocean.config_dict[\"web3_instance\"]\n    addr1 = web3.eth.account.create().address\n    compute_service.compute_values[\"publisherTrustedAlgorithmPublishers\"] = [addr1]\n\n    addr2 = web3.eth.account.create().address\n    # add a new trusted algorithm to the publisher_trusted_algorithms list\n    new_publisher_trusted_algo_publishers = (\n        compute_service.add_publisher_trusted_algorithm_publisher(addr2)\n    )\n\n    assert (\n        new_publisher_trusted_algo_publishers is not None\n    ), \"Added a new trusted algorithm failed. The list is empty.\"\n    assert len(new_publisher_trusted_algo_publishers) == 2\n\n    # add an existing algorithm to publisher_trusted_algorithms list\n    new_publisher_trusted_algo_publishers = (\n        compute_service.add_publisher_trusted_algorithm_publisher(addr2.upper())\n    )\n    assert len(new_publisher_trusted_algo_publishers) == 2\n\n    # remove an existing algorithm to publisher_trusted_algorithms list\n    new_publisher_trusted_algo_publishers = (\n        compute_service.remove_publisher_trusted_algorithm_publisher(addr2.upper())\n    )\n\n    assert len(new_publisher_trusted_algo_publishers) == 1\n\n    addr3 = web3.eth.account.create().address\n    # remove a trusted algorithm that does not belong to publisher_trusted_algorithms list\n    new_publisher_trusted_algo_publishers = (\n        compute_service.remove_publisher_trusted_algorithm_publisher(addr3)\n    )\n    assert len(new_publisher_trusted_algo_publishers) == 1\n\n\n@pytest.mark.unit\ndef test_inexistent_removals():\n    ddo_dict = get_sample_ddo_with_compute_service()\n    del ddo_dict[\"services\"][1][\"compute\"][\"publisherTrustedAlgorithms\"]\n    ddo = DDO.from_dict(ddo_dict)\n    compute_service = ddo.services[1]\n\n    with pytest.raises(\n        Exception, match=\"Algorithm notadid is not in trusted algorithms\"\n    ):\n        compute_service.remove_publisher_trusted_algorithm(\"notadid\")\n\n    ddo_dict = get_sample_ddo_with_compute_service()\n    del ddo_dict[\"services\"][1][\"compute\"][\"publisherTrustedAlgorithmPublishers\"]\n    ddo = DDO.from_dict(ddo_dict)\n    compute_service = ddo.services[1]\n\n    with pytest.raises(\n        Exception, match=\"Publisher notadid is not in trusted algorithm publishers\"\n    ):\n        compute_service.remove_publisher_trusted_algorithm_publisher(\"notadid\")\n\n\n@pytest.mark.unit\ndef test_utilitary_functions_for_trusted_algorithms(publisher_ocean):\n    \"\"\"Tests adding/removing trusted algorithms in the DDO metadata.\"\"\"\n    algorithm_ddo = get_sample_algorithm_ddo(filename=\"ddo_algorithm2.json\")\n    algorithm_ddo.did = \"did:op:123\"\n    algorithm_ddo_v2 = get_sample_algorithm_ddo(filename=\"ddo_algorithm2.json\")\n    algorithm_ddo_v2.did = \"did:op:1234\"\n    algorithm_ddo_v3 = get_sample_algorithm_ddo(filename=\"ddo_algorithm2.json\")\n    algorithm_ddo_v3.did = \"did:op:3333\"\n\n    ddo = DDO.from_dict(\n        get_sample_ddo_with_compute_service(\"ddo_v4_with_compute_service2.json\")\n    )\n\n    with patch(\"ocean_lib.assets.ddo.FileInfoProvider.fileinfo\") as mock:\n        the_response = Mock(spec=Response)\n        the_response.json.return_value = [\n            {\n                \"checksum\": \"5ce12db0cc7f13f963b1af3b5df7cab4fd3ffae16c8af7e6e416570d197dcc61\"\n            }\n        ]\n        mock.return_value = the_response\n        publisher_trusted_algorithms = [algorithm_ddo.generate_trusted_algorithms()]\n\n    assert len(publisher_trusted_algorithms) == 1\n    compute_service = ddo.services[1]\n    assert compute_service.type == \"compute\"\n    assert (\n        compute_service.compute_values[\"publisherTrustedAlgorithms\"]\n        == publisher_trusted_algorithms\n    )\n\n    with patch(\"ocean_lib.assets.ddo.FileInfoProvider.fileinfo\") as mock:\n        the_response = Mock(spec=Response)\n        the_response.json.return_value = [\n            {\n                \"checksum\": \"5ce12db0cc7f13f963b1af3b5df7cab4fd3ffae16c8af7e6e416570d197dcc61\"\n            }\n        ]\n        mock.return_value = the_response\n        new_publisher_trusted_algorithms = (\n            compute_service.add_publisher_trusted_algorithm(\n                algorithm_ddo_v2,\n            )\n        )\n\n    assert (\n        new_publisher_trusted_algorithms is not None\n    ), \"Added a new trusted algorithm failed. The list is empty.\"\n    assert len(new_publisher_trusted_algorithms) == 2\n\n    with patch(\"ocean_lib.assets.ddo.FileInfoProvider.fileinfo\") as mock:\n        the_response = Mock(spec=Response)\n        the_response.json.return_value = [\n            {\n                \"checksum\": \"5ce12db0cc7f13f963b1af3b5df7cab4fd3ffae16c8af7e6e416570d197dcc61\"\n            }\n        ]\n        mock.return_value = the_response\n        new_publisher_trusted_algorithms = (\n            compute_service.add_publisher_trusted_algorithm(algorithm_ddo)\n        )\n\n    assert new_publisher_trusted_algorithms is not None\n    for trusted_algorithm in publisher_trusted_algorithms:\n        assert (\n            trusted_algorithm[\"did\"] == algorithm_ddo.did\n        ), \"Added a different algorithm besides the existing ones.\"\n    assert len(new_publisher_trusted_algorithms) == 2\n\n\n@pytest.mark.unit\ndef test_add_trusted_algorithm_no_compute_service(publisher_ocean):\n    \"\"\"Tests if the DDO has or not a compute service.\"\"\"\n    algorithm_ddo = get_sample_algorithm_ddo(\"ddo_algorithm2.json\")\n    algorithm_ddo.did = \"did:op:0x666\"\n\n    ddo = DDO.from_dict(get_sample_ddo())\n    access_service = ddo.services[0]\n    assert access_service.type == \"access\"\n\n    with pytest.raises(AssertionError, match=\"Service is not compute type\"):\n        access_service.add_publisher_trusted_algorithm(algorithm_ddo)\n"
  },
  {
    "path": "ocean_lib/structures/abi_tuples.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n\"\"\"Defines NamedTuples `Stakes`, `OrderData`, `Operations`\"\"\"\nfrom enum import Enum\nfrom typing import NamedTuple\n\n\nclass OperationType(Enum):\n    SwapExactIn = 0\n    SwapExactOut = 1\n    FixedRate = 2\n    Dispenser = 3\n\n\nOperations = NamedTuple(\n    \"Operations\",\n    [\n        (\"exchange_id\", bytes),\n        (\"source\", str),\n        (\"operation\", OperationType),\n        (\"token_in\", str),\n        (\"amounts_in\", int),\n        (\"token_out\", str),\n        (\"amounts_out\", int),\n        (\"max_price\", int),\n        (\"swap_market_fee\", int),\n        (\"market_fee_address\", int),\n    ],\n)\n\nStakes = NamedTuple(\n    \"Stakes\",\n    [\n        (\"pool_address\", str),\n        (\"token_amount_in\", int),\n        (\"min_pool_amount_out\", int),\n    ],\n)\n\nOrderData = NamedTuple(\n    \"OrderData\",\n    [\n        (\"token_address\", str),\n        (\"consumer\", str),\n        (\"service_index\", int),\n        (\"provider_fees\", tuple),\n        (\"consume_fees\", tuple),\n    ],\n)\n\nReuseOrderData = NamedTuple(\n    \"ReuseOrderData\",\n    [\n        (\"token_address\", str),\n        (\"order_tx_id\", str),\n        (\"provider_fees\", tuple),\n    ],\n)\n\n\nMetadataProof = NamedTuple(\n    \"MetadataProof\",\n    [(\"validator_address\", str), (\"v\", int), (\"r\", bytes), (\"s\", bytes)],\n)\n"
  },
  {
    "path": "ocean_lib/structures/algorithm_metadata.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport json\nfrom typing import Any, Dict\n\nfrom enforce_typing import enforce_types\n\nfrom ocean_lib.services.consumer_parameters import ConsumerParameters\n\n\nclass AlgorithmMetadata:\n    @enforce_types\n    def __init__(self, metadata_dict: Dict[str, Any]) -> None:\n        \"\"\"Initialises AlgorithmMetadata object.\"\"\"\n        self.url = metadata_dict.get(\"url\", \"\")\n        self.rawcode = metadata_dict.get(\"rawcode\", \"\")\n        self.language = metadata_dict.get(\"language\", \"\")\n        self.format = metadata_dict.get(\"format\", \"\")\n        self.version = metadata_dict.get(\"version\", \"\")\n\n        container = metadata_dict.get(\"container\", dict())\n        self.container_entry_point = container.get(\"entrypoint\", \"\")\n        self.container_image = container.get(\"image\", \"\")\n        self.container_tag = container.get(\"tag\", \"\")\n        self.container_checksum = container.get(\"checksum\", \"\")\n\n        consumer_parameters = metadata_dict.get(\"consumerParameters\", [])\n        try:\n            self.consumer_parameters = [\n                ConsumerParameters.from_dict(cp_dict) for cp_dict in consumer_parameters\n            ]\n        except AttributeError:\n            raise TypeError(\"ConsumerParameters should be a list of dictionaries.\")\n\n    @enforce_types\n    def is_valid(self) -> bool:\n        return bool(\n            self.container_image and self.container_tag and self.container_entry_point\n        )\n\n    @enforce_types\n    def as_json_str(self) -> str:\n        return json.dumps(self.as_dictionary())\n\n    @enforce_types\n    def as_dictionary(self) -> Dict[str, Any]:\n        result = {\n            \"meta\": {\n                \"url\": self.url,\n                \"rawcode\": self.rawcode,\n                \"language\": self.language,\n                \"version\": self.version,\n                \"container\": {\n                    \"entrypoint\": self.container_entry_point,\n                    \"image\": self.container_image,\n                    \"tag\": self.container_tag,\n                    \"checksum\": self.container_checksum,\n                },\n            }\n        }\n\n        if self.consumer_parameters:\n            consumer_parameters = [x.as_dictionary() for x in self.consumer_parameters]\n            result[\"meta\"][\"consumerParameters\"] = consumer_parameters\n\n        return result\n"
  },
  {
    "path": "ocean_lib/structures/file_objects.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom abc import abstractmethod\nfrom typing import Optional, Protocol\n\nfrom enforce_typing import enforce_types\n\n\nclass FilesType(Protocol):\n    @enforce_types\n    @abstractmethod\n    def to_dict(self) -> dict:\n        raise NotImplementedError\n\n\nclass UrlFile(FilesType):\n    @enforce_types\n    def __init__(\n        self, url: str, method: Optional[str] = None, headers: Optional[dict] = None\n    ) -> None:\n        self.url = url\n        self.method = method\n        self.headers = headers\n        self.type = \"url\"\n\n    @enforce_types\n    def to_dict(self) -> dict:\n        result = {\"type\": self.type, \"url\": self.url}\n\n        if self.method:\n            result[\"method\"] = self.method\n\n        if self.headers:\n            result[\"headers\"] = self.headers\n\n        return result\n\n\nclass IpfsFile(FilesType):\n    @enforce_types\n    def __init__(self, hash: str) -> None:\n        self.hash = hash\n        self.type = \"ipfs\"\n\n    @enforce_types\n    def to_dict(self) -> dict:\n        return {\"type\": self.type, \"hash\": self.hash}\n\n\nclass GraphqlQuery(FilesType):\n    @enforce_types\n    def __init__(self, url: str, query: str, headers: Optional[dict] = None) -> None:\n        self.url = url\n        self.query = query\n        self.headers = headers\n        self.type = \"graphql\"\n\n    @enforce_types\n    def to_dict(self) -> dict:\n        result = {\"type\": self.type, \"url\": self.url, \"query\": self.query}\n\n        if self.headers:\n            result[\"headers\"] = self.headers\n\n        return result\n\n\nclass SmartContractCall(FilesType):\n    @enforce_types\n    def __init__(self, address: str, chainId: int, abi: dict) -> None:\n        self.address = address\n        self.abi = abi\n        self.chainId = chainId\n        self.type = \"smartcontract\"\n\n    @enforce_types\n    def to_dict(self) -> dict:\n        return {\n            \"type\": self.type,\n            \"address\": self.address,\n            \"abi\": self.abi,\n            \"chainId\": self.chainId,\n        }\n\n\nclass ArweaveFile(FilesType):\n    @enforce_types\n    def __init__(self, transaction_id: str) -> None:\n        self.transaction_id = transaction_id\n        self.type = \"arweave\"\n\n    @enforce_types\n    def to_dict(self) -> dict:\n        return {\"type\": self.type, \"transactionId\": self.transaction_id}\n\n\n@enforce_types\ndef FilesTypeFactory(file_obj: dict) -> FilesType:\n    \"\"\"Factory Method\"\"\"\n    if file_obj[\"type\"] == \"url\":\n        return UrlFile(\n            file_obj[\"url\"],\n            method=file_obj.get(\"method\", \"GET\"),\n            headers=file_obj.get(\"headers\"),\n        )\n    elif file_obj[\"type\"] == \"arweave\":\n        return ArweaveFile(file_obj[\"transactionId\"])\n    elif file_obj[\"type\"] == \"ipfs\":\n        return IpfsFile(file_obj[\"hash\"])\n    elif file_obj[\"type\"] == \"graphql\":\n        return GraphqlQuery(file_obj[\"url\"], query=file_obj.get(\"query\"))\n    elif file_obj[\"type\"] == \"smartcontract\":\n        return SmartContractCall(\n            address=file_obj.get(\"address\"),\n            chainId=file_obj.get(\"chainId\"),\n            abi=file_obj.get(\"abi\"),\n        )\n    else:\n        raise Exception(\"Unrecognized file type\")\n"
  },
  {
    "path": "ocean_lib/structures/test/test_algorithm_metadata.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport json\n\nimport pytest\n\nfrom ocean_lib.structures.algorithm_metadata import AlgorithmMetadata\n\n\n@pytest.mark.unit\ndef test_algorithm_metadata():\n    algo_metadata = AlgorithmMetadata(\n        {\n            \"rawcode\": \"\",\n            \"language\": \"Node.js\",\n            \"format\": \"docker-image\",\n            \"version\": \"0.1\",\n            \"container\": {\n                \"entrypoint\": \"node $ALGO\",\n                \"image\": \"ubuntu\",\n                \"tag\": \"latest\",\n                \"checksum\": \"44e10daa6637893f4276bb8d7301eb35306ece50f61ca34dcab550\",\n            },\n            \"consumerParameters\": [\n                {\n                    \"name\": \"some_key\",\n                    \"type\": \"string\",\n                    \"label\": \"test_key_label\",\n                    \"required\": True,\n                    \"default\": \"value\",\n                    \"description\": \"this is a test key\",\n                }\n            ],\n        }\n    )\n\n    assert algo_metadata.is_valid()\n    assert \"rawcode\" in json.loads(algo_metadata.as_json_str())[\"meta\"]\n\n    assert (\n        algo_metadata.as_dictionary()[\"meta\"][\"consumerParameters\"][0][\"name\"]\n        == \"some_key\"\n    )\n"
  },
  {
    "path": "ocean_lib/structures/test/test_file_objects.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\nfrom ocean_lib.structures.file_objects import FilesTypeFactory, IpfsFile, UrlFile\n\n\n@pytest.mark.unit\ndef test_url_file():\n    url_file = UrlFile(url=\"https://url.com\")\n    assert url_file.to_dict() == {\"type\": \"url\", \"url\": \"https://url.com\"}\n\n    url_file = UrlFile(url=\"https://url.com\", method=\"POST\")\n    assert url_file.to_dict() == {\n        \"type\": \"url\",\n        \"url\": \"https://url.com\",\n        \"method\": \"POST\",\n    }\n\n    url_file = UrlFile(url=\"https://url.com\", headers={\"test\": \"test_header\"})\n    assert url_file.to_dict() == {\n        \"type\": \"url\",\n        \"url\": \"https://url.com\",\n        \"headers\": {\"test\": \"test_header\"},\n    }\n\n\n@pytest.mark.unit\ndef test_ipfs_file():\n    ipfs_file = IpfsFile(hash=\"abc\")\n    assert ipfs_file.to_dict() == {\"type\": \"ipfs\", \"hash\": \"abc\"}\n\n\n@pytest.mark.unit\ndef test_filetype_factory():\n    factory_file = FilesTypeFactory(\n        {\n            \"type\": \"url\",\n            \"url\": \"https://url.com\",\n            \"method\": \"GET\",\n        }\n    )\n\n    assert factory_file.url == \"https://url.com\"\n\n    factory_file = FilesTypeFactory(\n        {\n            \"type\": \"ipfs\",\n            \"hash\": \"abc\",\n        }\n    )\n\n    assert factory_file.hash == \"abc\"\n\n    with pytest.raises(Exception):\n        factory_file = FilesTypeFactory({\"type\": \"somethingelse\"})\n"
  },
  {
    "path": "ocean_lib/test/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "ocean_lib/test/test_config.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport json\n\nimport pytest\nfrom web3 import Web3\n\nfrom ocean_lib.ocean.ocean import Ocean\n\n\n@pytest.mark.unit\ndef test_metadataCacheUri_config_key():\n    \"\"\"Tests that the metadata_cache_uri config property can be set using the\n    `metadataCacheUri` config dict key when created via the Ocean __init__\"\"\"\n    config_dict = {\n        \"NETWORK_NAME\": \"development\",\n        \"METADATA_CACHE_URI\": \"http://v4.aquarius.oceanprotocol.com\",\n        \"PROVIDER_URL\": \"http://172.15.0.4:8030\",\n        \"DOWNLOADS_PATH\": \"consume-downloads\",\n        \"ADDRESS_FILE\": \"~/.ocean/ocean-contracts/artifacts/address.json\",\n        \"CHAIN_ID\": 8996,\n        \"web3_instance\": Web3(),\n    }\n    ocean_instance = Ocean(config_dict=config_dict)\n    assert (\n        \"http://v4.aquarius.oceanprotocol.com\"\n        == ocean_instance.config_dict[\"METADATA_CACHE_URI\"]\n    )\n\n\n@pytest.mark.unit\ndef test_incomplete():\n    \"\"\"Tests that the metadata_cache_uri config property can be set using the\n    `metadataCacheUri` config dict key when created via the Ocean __init__\"\"\"\n    config_dict = {\n        \"METADATA_CACHE_URI\": \"http://ItWorked.com\",\n    }\n\n    with pytest.raises(Exception) as exception_info:\n        Ocean(config_dict=config_dict)\n\n    exception_response = json.loads(exception_info.value.args[0])\n    assert exception_response[\"NETWORK_NAME\"] == \"required\"\n"
  },
  {
    "path": "ocean_lib/test/test_example_config.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pytest\n\nfrom ocean_lib.example_config import (\n    DEFAULT_METADATA_CACHE_URI,\n    DEFAULT_PROVIDER_URL,\n    METADATA_CACHE_URI,\n    get_config_dict,\n)\nfrom ocean_lib.models.data_nft_factory import DataNFTFactoryContract\nfrom ocean_lib.ocean.util import get_address_of_type\nfrom ocean_lib.web3_internal.contract_utils import get_contracts_addresses\n\n\n@pytest.mark.unit\ndef test_ganache_example_config():\n    \"\"\"Tests the config structure of ganache network.\"\"\"\n\n    config = get_config_dict()\n\n    assert config[\"METADATA_CACHE_URI\"] == DEFAULT_METADATA_CACHE_URI\n    assert config[\"PROVIDER_URL\"] == DEFAULT_PROVIDER_URL\n\n\n@pytest.mark.unit\ndef test_polygon_example_config():\n    \"\"\"Tests the config structure of Polygon network.\"\"\"\n    config = get_config_dict(\"https://polygon-rpc.com\")\n\n    assert config[\"METADATA_CACHE_URI\"] == METADATA_CACHE_URI\n    assert config[\"PROVIDER_URL\"] == \"https://v4.provider.polygon.oceanprotocol.com\"\n\n\n@pytest.mark.unit\ndef test_get_address_of_type():\n    config = get_config_dict(\"https://polygon-rpc.com\")\n\n    data_nft_factory = get_address_of_type(config, DataNFTFactoryContract.CONTRACT_NAME)\n    addresses = get_contracts_addresses(config)\n    assert addresses[DataNFTFactoryContract.CONTRACT_NAME] == data_nft_factory\n"
  },
  {
    "path": "ocean_lib/web3_internal/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "ocean_lib/web3_internal/clef.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport sys\nfrom pathlib import Path\n\nfrom web3 import HTTPProvider, IPCProvider\nfrom web3.main import Web3\n\n\ndef get_clef_accounts(uri: str = None, timeout: int = 120) -> None:\n    provider = None\n    if uri is None:\n        if sys.platform == \"win32\":\n            uri = \"http://localhost:8550/\"\n        else:\n            uri = Path.home().joinpath(\".clef/clef.ipc\").as_posix()\n    try:\n        if Path(uri).exists():\n            provider = IPCProvider(uri, timeout=timeout)\n    except OSError:\n        if uri is not None and uri.startswith(\"http\"):\n            provider = HTTPProvider(uri, {\"timeout\": timeout})\n    if provider is None:\n        raise ValueError(\n            \"Unknown URI, must be IPC socket path or URL starting with 'http'\"\n        )\n\n    response = provider.make_request(\"account_list\", [])\n    if \"error\" in response:\n        raise ValueError(response[\"error\"][\"message\"])\n\n    clef_accounts = [ClefAccount(address, provider) for address in response[\"result\"]]\n    return clef_accounts\n\n\nclass ClefAccount:\n    def __init__(self, address: str, provider: [HTTPProvider, IPCProvider]) -> None:\n        self.address = Web3.to_checksum_address(address)\n        self.provider = provider\n"
  },
  {
    "path": "ocean_lib/web3_internal/constants.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n\"\"\"\nThis module holds following default values and constants.\n\n\"\"\"\n\nZERO_ADDRESS = \"0x0000000000000000000000000000000000000000\"\n\nMAX_UINT256 = 2**256 - 1\n\nMAX_INT256 = 2**255 - 1\nMIN_INT256 = 2**255 * -1\n"
  },
  {
    "path": "ocean_lib/web3_internal/contract_base.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n\"\"\"All contracts inherit from `ContractBase` class.\"\"\"\nimport logging\nfrom typing import List, Optional\n\nfrom enforce_typing import enforce_types\nfrom eth_typing import ChecksumAddress\nfrom web3._utils.abi import abi_to_signature\nfrom web3.exceptions import MismatchedABI\nfrom web3.logs import DISCARD\nfrom web3.main import Web3\n\nfrom ocean_lib.web3_internal.clef import ClefAccount\nfrom ocean_lib.web3_internal.contract_utils import load_contract\n\nlogger = logging.getLogger(__name__)\n\n\ndef function_wrapper(contract, web3, contract_functions, func_name):\n    # direct function calls\n    if hasattr(contract, func_name):\n        return getattr(contract, func_name)\n\n    # contract functions\n    def wrap(*args, **kwargs):\n        args2 = list(args)\n\n        tx_dict = None\n\n        # retrieve tx dict from either args or kwargs\n        if args and isinstance(args[-1], dict):\n            tx_dict = args[-1] if args[-1].get(\"from\") else None\n            args2 = list(args[:-1])\n\n        if \"tx_dict\" in kwargs:\n            tx_dict = kwargs[\"tx_dict\"] if kwargs[\"tx_dict\"].get(\"from\") else None\n            del kwargs[\"tx_dict\"]\n\n        # use addresses instead of wallets when doing the call\n        for arg in args2:\n            if hasattr(arg, \"address\"):\n                args2 = list(args2)\n                args2[args2.index(arg)] = arg.address\n\n        func = getattr(contract_functions, func_name)\n        result = func(*args2, **kwargs)\n        func_signature = abi_to_signature(result.abi)\n\n        # view/pure functions don't need \"from\" key in tx_dict\n        if not tx_dict and result.abi[\"stateMutability\"] not in [\"view\", \"pure\"]:\n            raise Exception(\"Needs tx_dict with 'from' key.\")\n\n        # if it's a view/pure function, just call it\n        if result.abi[\"stateMutability\"] in [\"view\", \"pure\"]:\n            return result.call()\n        else:\n            # if it's a transaction, build and send it\n            wallet = tx_dict[\"from\"]\n            tx_dict2 = tx_dict.copy()\n            tx_dict2[\"nonce\"] = web3.eth.get_transaction_count(wallet.address)\n            tx_dict2[\"from\"] = tx_dict[\"from\"].address\n\n            result = result.build_transaction(tx_dict2)\n\n            # sign with wallet private key and send transaction\n            if isinstance(wallet, ClefAccount):\n                for k, v in result.items():\n                    result[k] = Web3.to_hex(v) if not isinstance(v, str) else v\n\n                raw_signed_tx = wallet.provider.make_request(\n                    \"account_signTransaction\", [result, func_signature]\n                )\n\n                raw_signed_tx = raw_signed_tx[\"result\"][\"raw\"]\n            else:\n                signed_tx = web3.eth.account.sign_transaction(\n                    result, wallet._private_key\n                )\n                raw_signed_tx = signed_tx.rawTransaction\n\n            receipt = web3.eth.send_raw_transaction(raw_signed_tx)\n\n            return web3.eth.wait_for_transaction_receipt(receipt)\n\n    return wrap\n\n\nclass ContractBase(object):\n\n    \"\"\"Base class for all contract objects.\"\"\"\n\n    CONTRACT_NAME = None\n\n    @enforce_types\n    def __init__(self, config_dict: dict, address: Optional[str]) -> None:\n        \"\"\"Initialises Contract Base object.\"\"\"\n        assert (\n            self.contract_name\n        ), \"contract_name property needs to be implemented in subclasses.\"\n\n        self.config_dict = config_dict\n\n        self.contract = load_contract(\n            config_dict[\"web3_instance\"], self.contract_name, address\n        )\n        assert not address or (self.contract.address.lower() == address.lower())\n\n        transferable = [\n            x for x in dir(self.contract.functions) if not x.startswith(\"_\")\n        ]\n\n        # transfer contract functions to ContractBase object\n        for function in transferable:\n            setattr(\n                self,\n                function,\n                function_wrapper(\n                    self.contract,\n                    config_dict[\"web3_instance\"],\n                    self.contract.functions,\n                    function,\n                ),\n            )\n\n    @enforce_types\n    def __str__(self) -> str:\n        \"\"\"Returns contract `name @ address.`\"\"\"\n        return f\"{self.contract_name} @ {self.address}\"\n\n    @property\n    @enforce_types\n    def contract_name(self) -> str:\n        \"\"\"Returns the contract name\"\"\"\n        return self.CONTRACT_NAME\n\n    @staticmethod\n    @enforce_types\n    def to_checksum_address(address: str) -> ChecksumAddress:\n        \"\"\"\n        Validate the address provided.\n\n        :param address: Address, hex str\n        :return: address, hex str\n        \"\"\"\n        return Web3.to_checksum_address(address.lower())\n\n    @enforce_types\n    def get_event_signature(self, event_name: str) -> str:\n        try:\n            e = getattr(self.contract.events, event_name)\n        except MismatchedABI:\n            raise ValueError(\n                f\"Event {event_name} not found in {self.CONTRACT_NAME} contract.\"\n            )\n\n        abi = e().abi\n        types = [param[\"type\"] for param in abi[\"inputs\"]]\n        sig_str = f'{event_name}({\",\".join(types)})'\n\n        return Web3.keccak(text=sig_str).hex()\n\n    @enforce_types\n    def get_logs(\n        self,\n        event_name: str,\n        from_block: Optional[int] = 0,\n        to_block: Optional[int] = \"latest\",\n    ) -> List:\n        topic = self.get_event_signature(event_name)\n        web3 = self.config_dict[\"web3_instance\"]\n\n        event_filter = web3.eth.filter(\n            {\n                \"topics\": [topic],\n                \"toBlock\": to_block,\n                \"fromBlock\": from_block,\n            }\n        )\n\n        events = []\n\n        for log in event_filter.get_all_entries():\n            receipt = web3.eth.wait_for_transaction_receipt(log.transactionHash)\n            fn = getattr(self.contract.events, event_name)\n            processed_events = fn().process_receipt(receipt, errors=DISCARD)\n            for processed_event in processed_events:\n                events.append(processed_event)\n\n        return events\n"
  },
  {
    "path": "ocean_lib/web3_internal/contract_utils.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport json\nimport logging\nimport os\nfrom pathlib import Path\nfrom typing import Any, Dict, Optional\n\nfrom enforce_typing import enforce_types\nfrom web3.contract import Contract\nfrom web3.main import Web3\n\nimport artifacts  # noqa\n\nlogger = logging.getLogger(__name__)\nGANACHE_URL = \"http://127.0.0.1:8545\"\n\n\n@enforce_types\ndef get_contract_definition(contract_name: str) -> Dict[str, Any]:\n    \"\"\"Returns the abi JSON for a contract name.\"\"\"\n    path = os.path.join(artifacts.__file__, \"..\", f\"{contract_name}.json\")\n    path = Path(path).expanduser().resolve()\n\n    if not path.exists():\n        raise TypeError(\"Contract name does not exist in artifacts.\")\n\n    with open(path) as f:\n        return json.load(f)\n\n\n@enforce_types\ndef load_contract(web3: Web3, contract_name: str, address: Optional[str]) -> Contract:\n    \"\"\"Loads a contract using its name and address.\"\"\"\n    contract_definition = get_contract_definition(contract_name)\n    abi = contract_definition[\"abi\"]\n    bytecode = contract_definition[\"bytecode\"]\n\n    return web3.eth.contract(address=address, abi=abi, bytecode=bytecode)\n\n\n@enforce_types\ndef get_contracts_addresses_all_networks(config: dict):\n    \"\"\"Get addresses, across *all* networks, from info in ADDRESS_FILE\"\"\"\n    address_file = config.get(\"ADDRESS_FILE\")\n    address_file = os.path.expanduser(address_file) if address_file else None\n\n    if not address_file or not os.path.exists(address_file):\n        raise Exception(f\"Could not find address_file={address_file}.\")\n    with open(address_file) as f:\n        addresses = json.load(f)\n\n    return addresses\n\n\n@enforce_types\ndef get_contracts_addresses(config: dict) -> Optional[Dict[str, str]]:\n    \"\"\"Get addresses for given NETWORK_NAME, from info in ADDRESS_FILE\"\"\"\n    network_name = config[\"NETWORK_NAME\"]\n\n    addresses = get_contracts_addresses_all_networks(config)\n\n    network_addresses = [val for key, val in addresses.items() if key == network_name]\n\n    if not network_addresses:\n        address_file = config.get(\"ADDRESS_FILE\")\n        raise Exception(\n            f\"Address not found for network_name={network_name}.\"\n            f\" Please check your address_file={address_file}.\"\n        )\n\n    return _checksum_contract_addresses(network_addresses=network_addresses[0])\n\n\n@enforce_types\n# Check singnet/snet-cli#142 (comment). You need to provide a lowercase address then call web3.to_checksum_address()\n# for software safety.\ndef _checksum_contract_addresses(\n    network_addresses: Dict[str, Any]\n) -> Optional[Dict[str, Any]]:\n    for key, value in network_addresses.items():\n        if key == \"chainId\":\n            continue\n        if isinstance(value, int):\n            continue\n        if isinstance(value, dict):\n            for k, v in value.items():\n                value.update({k: Web3.to_checksum_address(v.lower())})\n        else:\n            network_addresses.update({key: Web3.to_checksum_address(value.lower())})\n\n    return network_addresses\n"
  },
  {
    "path": "ocean_lib/web3_internal/http_provider.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom web3 import HTTPProvider, WebsocketProvider\n\nfrom ocean_lib.web3_internal.request import make_post_request\n\nGANACHE_URL = \"http://127.0.0.1:8545\"\nPOLYGON_URL = \"https://rpc.polygon.oceanprotocol.com\"\n\nSUPPORTED_NETWORK_NAMES = {\n    \"rinkeby\",\n    \"kovan\",\n    \"ganache\",\n    \"mainnet\",\n    \"ropsten\",\n    \"polygon\",\n}\n\n\nclass CustomHTTPProvider(HTTPProvider):\n    \"\"\"\n    Override requests to control the connection pool to make it blocking.\n    \"\"\"\n\n    def make_request(self, method, params):\n        self.logger.debug(\n            \"Making request HTTP. URI: %s, Method: %s\", self.endpoint_uri, method\n        )\n        request_data = self.encode_rpc_request(method, params)\n        raw_response = make_post_request(\n            self.endpoint_uri, request_data, **self.get_request_kwargs()\n        )\n        response = self.decode_rpc_response(raw_response)\n        self.logger.debug(\n            \"Getting response HTTP. URI: %s, \" \"Method: %s, Response: %s\",\n            self.endpoint_uri,\n            method,\n            response,\n        )\n        return response\n\n\ndef get_web3_connection_provider(network_url):\n    if network_url.startswith(\"http\"):\n        return CustomHTTPProvider(network_url)\n    elif network_url.startswith(\"ws\"):\n        return WebsocketProvider(network_url)\n\n    raise Exception(f\"Unsupported network url: {network_url}\")\n"
  },
  {
    "path": "ocean_lib/web3_internal/request.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\"\"\"\nThis is copied from Web3 python library to control the `requests`\nsession parameters.\n\"\"\"\n\nimport lru\nimport requests\nfrom requests.adapters import HTTPAdapter\nfrom web3._utils.caching import generate_cache_key\n\n\ndef _remove_session(key, session):\n    session.close()\n\n\n_session_cache = lru.LRU(8, callback=_remove_session)\n\n\ndef _get_session(*args, **kwargs):\n    cache_key = generate_cache_key((args, kwargs))\n    if cache_key not in _session_cache:\n        # This is the main change from original Web3 `_get_session`\n        session = requests.sessions.Session()\n        session.mount(\n            \"http://\",\n            HTTPAdapter(pool_connections=25, pool_maxsize=25, pool_block=True),\n        )\n        session.mount(\n            \"https://\",\n            HTTPAdapter(pool_connections=25, pool_maxsize=25, pool_block=True),\n        )\n        _session_cache[cache_key] = session\n    return _session_cache[cache_key]\n\n\ndef make_post_request(endpoint_uri, data, *args, **kwargs):\n    kwargs.setdefault(\"timeout\", 10)\n    session = _get_session(endpoint_uri)\n\n    version = \"TODO\"  # TODO\n    version_header = {\"User-Agent\": f\"OceanAquarius/{version}\"}\n\n    if \"headers\" in kwargs:\n        kwargs[\"headers\"].update(version_header)\n    else:\n        kwargs[\"headers\"] = version_header\n\n    response = session.post(endpoint_uri, data=data, *args, **kwargs)\n    response.raise_for_status()\n\n    return response.content\n"
  },
  {
    "path": "ocean_lib/web3_internal/test/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "ocean_lib/web3_internal/test/conftest.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n# Directory ../ocean_lib/models/test holds test_btoken.py and more.\n# Those tests grab ../ocean_lib/models/test/conftest.py, which\n#  sets up convenient-to-use wallets/accounts for Alice & Bob, datatokens, more.\n# *This* directory wants similar items. To avoid code repetition,\n#  here we simply import that conftest's contents.\nfrom conftest_ganache import *\n"
  },
  {
    "path": "ocean_lib/web3_internal/test/test_contract_base.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\nimport pytest\n\nfrom ocean_lib.ocean.util import get_address_of_type\nfrom ocean_lib.web3_internal.constants import ZERO_ADDRESS\nfrom ocean_lib.web3_internal.contract_base import ContractBase\nfrom tests.resources.helper_functions import get_wallet\n\n\nclass MyFactory(ContractBase):\n    CONTRACT_NAME = \"ERC721Factory\"\n\n\n@pytest.mark.unit\ndef test_name_is_None(config):\n    with pytest.raises(Exception):\n        # self.name will become None, triggering the error\n        ContractBase(config, None)\n\n\n@pytest.mark.unit\ndef test_main(config):\n    alice_wallet = get_wallet(1)\n    # test super-simple functionality of child\n    nft_factory_address = get_address_of_type(config, \"ERC721Factory\")\n    factory = MyFactory(config, nft_factory_address)\n    factory.deployERC721Contract(\n        \"NFT\",\n        \"NFTS\",\n        1,\n        ZERO_ADDRESS,\n        ZERO_ADDRESS,\n        \"http://someurl\",\n        True,\n        alice_wallet.address,\n        {\"from\": alice_wallet},\n    )\n\n    # test attributes\n    assert factory.contract_name == \"ERC721Factory\"\n    assert factory.contract is not None\n    assert factory.contract.address == nft_factory_address\n    assert ContractBase.to_checksum_address(nft_factory_address) == nft_factory_address\n\n    # test methods\n    assert factory.contract_name == \"ERC721Factory\"\n    assert factory.address == nft_factory_address\n    assert str(factory) == f\"{factory.contract_name} @ {factory.address}\"\n    assert factory.createToken\n    assert factory.getCurrentTokenCount\n    assert factory.getTokenTemplate\n"
  },
  {
    "path": "ocean_lib/web3_internal/test/test_contract_utils.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom web3.main import Web3\n\nfrom ocean_lib.web3_internal.contract_utils import _checksum_contract_addresses\n\n\ndef test_checksum_contract_addresses(monkeypatch):\n    addresses = {\n        \"chainId\": 7,\n        \"test\": \"0x20802d1a9581b94e51db358c09e0818d6bd071b4\",\n        \"dict\": {\"address\": \"0xe2dd09d719da89e5a3d0f2549c7e24566e947260\"},\n    }\n    assert Web3.is_checksum_address(addresses[\"test\"]) is False\n    assert Web3.is_checksum_address(addresses[\"dict\"][\"address\"]) is False\n    checksum_addresses = _checksum_contract_addresses(addresses)\n    assert Web3.is_checksum_address(checksum_addresses[\"test\"]) is True\n    assert Web3.is_checksum_address(addresses[\"dict\"][\"address\"]) is True\n"
  },
  {
    "path": "ocean_lib/web3_internal/test/test_wallet.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport os\n\nimport pytest\n\nfrom ocean_lib.ocean.util import to_wei\nfrom tests.resources.helper_functions import generate_wallet\n\n\n@pytest.mark.unit\ndef test_generating_wallets(ocean_token, config):\n    generated_wallet = generate_wallet()\n    assert generated_wallet.address, \"Wallet has not an address.\"\n\n    assert config[\"web3_instance\"].eth.get_balance(generated_wallet.address) == to_wei(\n        3\n    )\n\n    assert ocean_token.balanceOf(generated_wallet.address) == to_wei(50)\n\n    env_key_labels = [\n        \"TEST_PRIVATE_KEY1\",\n        \"TEST_PRIVATE_KEY2\",\n        \"TEST_PRIVATE_KEY3\",\n        \"TEST_PRIVATE_KEY4\",\n        \"TEST_PRIVATE_KEY5\",\n        \"TEST_PRIVATE_KEY6\",\n        \"TEST_PRIVATE_KEY7\",\n        \"TEST_PRIVATE_KEY8\",\n        \"FACTORY_DEPLOYER_PRIVATE_KEY\",\n        \"PROVIDER_PRIVATE_KEY\",\n    ]\n    env_private_keys = []\n    for key_label in env_key_labels:\n        key = os.environ.get(key_label)\n        env_private_keys.append(key)\n    assert generated_wallet._private_key.hex() not in env_private_keys\n"
  },
  {
    "path": "ocean_lib/web3_internal/utils.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport logging\nfrom collections import namedtuple\nfrom typing import Any, Union\n\nimport requests\nfrom enforce_typing import enforce_types\nfrom eth_keys import KeyAPI\nfrom eth_keys.backends import NativeECCBackend\nfrom hexbytes.main import HexBytes\nfrom web3.main import Web3\n\nfrom ocean_lib.web3_internal.clef import ClefAccount\n\nSignature = namedtuple(\"Signature\", (\"v\", \"r\", \"s\"))\n\nlogger = logging.getLogger(__name__)\nkeys = KeyAPI(NativeECCBackend)\n\n\n@enforce_types\ndef to_32byte_hex(val: int) -> str:\n    \"\"\"\n\n    :param val:\n    :return:\n    \"\"\"\n    return Web3.to_hex(Web3.to_bytes(val).rjust(32, b\"\\0\"))\n\n\n@enforce_types\ndef sign_with_clef(message_hash: str, wallet: ClefAccount) -> str:\n    message_hash = Web3.solidity_keccak(\n        [\"bytes\"],\n        [Web3.to_bytes(text=message_hash)],\n    )\n\n    orig_sig = wallet.provider.make_request(\n        \"account_signData\", [\"data/plain\", wallet.address, message_hash.hex()]\n    )[\"result\"]\n\n    return orig_sig\n\n\n@enforce_types\ndef sign_with_key(message_hash: Union[HexBytes, str], key: str) -> str:\n    if isinstance(message_hash, str):\n        message_hash = Web3.solidity_keccak(\n            [\"bytes\"],\n            [Web3.to_bytes(text=message_hash)],\n        )\n\n    pk = keys.PrivateKey(Web3.to_bytes(hexstr=key))\n\n    prefix = \"\\x19Ethereum Signed Message:\\n32\"\n    signable_hash = Web3.solidity_keccak(\n        [\"bytes\", \"bytes\"], [Web3.to_bytes(text=prefix), Web3.to_bytes(message_hash)]\n    )\n\n    return keys.ecdsa_sign(message_hash=signable_hash, private_key=pk)\n\n\n@enforce_types\ndef split_signature(signature: Any) -> Signature:\n    \"\"\"\n\n    :param web3:\n    :param signature: signed message hash, hex str\n    :return:\n    \"\"\"\n    assert len(signature) == 65, (\n        f\"invalid signature, \" f\"expecting bytes of length 65, got {len(signature)}\"\n    )\n    v = Web3.to_int(signature[-1])\n    r = to_32byte_hex(int.from_bytes(signature[:32], \"big\"))\n    s = to_32byte_hex(int.from_bytes(signature[32:64], \"big\"))\n    if v != 27 and v != 28:\n        v = 27 + v % 2\n\n    return Signature(v, r, s)\n\n\n@enforce_types\ndef get_gas_fees() -> tuple:\n    # Polygon & Mumbai uses EIP-1559. So, dynamically determine priority fee\n    gas_resp = requests.get(\"https://gasstation.polygon.technology/v2\")\n\n    return (\n        Web3.to_wei(gas_resp.json()[\"fast\"][\"maxPriorityFee\"], \"gwei\"),\n        Web3.to_wei(gas_resp.json()[\"fast\"][\"maxFee\"], \"gwei\"),\n    )\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.black]\nexclude = '''\n(\n  setup.py\n)\n'''"
  },
  {
    "path": "pytest.ini",
    "content": "# pytest.ini\n[pytest]\nfilterwarnings =\n    ignore::DeprecationWarning\n    ignore:.*The event signature did not match the provided ABI*:UserWarning\n    ignore:.*Event log does not contain enough topics for the given ABI.*:UserWarning\n\nmarkers =\n    nosetup_all: do not call setup_all\n    unit\n    integration\n\n# generated readmes run through test_readmes.py, separately and programatically\naddopts = --ignore=tests/generated-readmes\n\nenv =\n    D:TEST_PRIVATE_KEY1=0x8467415bb2ba7c91084d932276214b11a3dd9bdb2930fefa194b666dd8020b99\n    D:TEST_PRIVATE_KEY2=0x1d751ded5a32226054cd2e71261039b65afb9ee1c746d055dd699b1150a5befc\n    D:TEST_PRIVATE_KEY3=0x732fbb7c355aa8898f4cff92fa7a6a947339eaf026a08a51f171199e35a18ae0\n    D:TEST_PRIVATE_KEY4=0xef4b441145c1d0f3b4bc6d61d29f5c6e502359481152f869247c7a4244d45209\n    D:TEST_PRIVATE_KEY5=0x5d75837394b078ce97bc289fa8d75e21000573520bfa7784a9d28ccaae602bf8\n    D:TEST_PRIVATE_KEY6=0x1f990f8b013fc5c7955e0f8746f11ded231721b9cf3f99ff06cdc03492b28090\n    D:TEST_PRIVATE_KEY7=0x8683d6511213ac949e093ca8e9179514d4c56ce5ea9b83068f723593f913b1ab\n    D:TEST_PRIVATE_KEY8=0x1263dc73bef43a9da06149c7e598f52025bf4027f1d6c13896b71e81bb9233fb\n    D:FACTORY_DEPLOYER_PRIVATE_KEY=0xc594c6e5def4bab63ac29eed19a134c130388f74f019bc74b8f4389df2837a58\n    D:PROVIDER_PRIVATE_KEY=0xfd5c1ccea015b6d663618850824154a3b3fb2882c46cefb05b9a93fea8c3d215\n    D:ADDRESS_FILE=~/.ocean/ocean-contracts/artifacts/address.json\n"
  },
  {
    "path": "requirements_dev.txt",
    "content": "# pip install ocean-lib\n# is for end users. That will install the packages\n# listed in the install_requires list of setup.py.\n\n# pip install -r requirements_dev.txt\n# is for the developers of ocean-lib, so doing that should\n# install all the Python packages listed\n# in the install_requires list of setup.py\n# and also the 'dev' list in the extras_require dict\n\n-e .[dev]\n\n"
  },
  {
    "path": "setup.cfg",
    "content": "# Here is how we view setup.cfg vs setup.py: https://stackoverflow.com/a/46090408/14214722\n\n[bdist_wheel]\nuniversal = 1\n\n[aliases]\n# Define setup.py command aliases here\ntest = pytest\n\n[tool:pytest]\ncollect_ignore = ['setup.py']\n\n[isort]\nmulti_line_output = 3\ninclude_trailing_comma = True\nforce_grid_wrap = 0\nuse_parentheses = True\nensure_newline_before_comments = True\nline_length = 88\n\n[flake8]\nmax-line-length=8888888888\nignore=E203,W503\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\n\"\"\"The setup script.\"\"\"\n\n#  Copyright 2018 Ocean Protocol Foundation\n#  SPDX-License-Identifier: Apache-2.0\n\nfrom setuptools import find_namespace_packages, setup\n\nwith open(\"README.md\", encoding=\"utf8\") as readme_file:\n    readme = readme_file.read()\n\n# Installed by pip install ocean-lib\n# or pip install -e .\ninstall_requirements = [\n    \"ocean-contracts==2.0.3\",\n    \"coloredlogs==15.0.1\",\n    \"requests>=2.21.0\",\n    \"pytz\",  # used minimally and unlikely to change, common dependency\n    \"enforce-typing==1.0.0.post1\",\n    \"eciespy==0.4.1\",\n    \"cryptography==41.0.7\",\n    \"web3==6.14.0\",\n    # web3.py requires eth-abi, requests, and more,\n    # so those will be installed too.\n    # See https://github.com/ethereum/web3.py/blob/master/setup.py\n]\n# Required to run setup.py:\nsetup_requirements = [\"pytest-runner\"]\n\ntest_requirements = [\n    \"codacy-coverage==1.3.11\",\n    \"coverage==7.4.1\",\n    \"mccabe==0.7.0\",\n    \"pytest==8.0.0\",\n    \"pytest-watch==4.2.0\",\n    \"pytest-env\",  # common dependency\n    \"matplotlib\",  # just used in a readme test and unlikely to change, common dependency\n    \"mkcodes==0.1.1\",\n    \"pytest-sugar==0.9.7\",\n]\n\n# Possibly required by developers of ocean-lib:\ndev_requirements = [\n    \"bumpversion==0.6.0\",\n    \"pkginfo==1.9.6\",\n    \"twine==4.0.2\",\n    \"watchdog==3.0.0\",\n    \"isort\",\n    \"flake8\",\n    \"black\",\n    \"pre-commit\",\n    \"licenseheaders\",\n]\n\npackages = find_namespace_packages(include=[\"ocean_lib*\"], exclude=[\"*test*\"])\n\nsetup(\n    author=\"ocean-core-team\",\n    author_email=\"devops@oceanprotocol.com\",\n    classifiers=[\n        \"Development Status :: 4 - Beta\",\n        \"Intended Audience :: Developers\",\n        \"License :: OSI Approved :: Apache Software License\",\n        \"Natural Language :: English\",\n        \"Programming Language :: Python :: 3.8\",\n    ],\n    description=\"🐳 Ocean protocol library.\",\n    extras_require={\n        \"test\": test_requirements,\n        \"dev\": dev_requirements + test_requirements,\n    },\n    install_requires=install_requirements,\n    license=\"Apache Software License 2.0\",\n    long_description=readme,\n    long_description_content_type=\"text/markdown\",\n    include_package_data=True,\n    keywords=\"ocean-lib\",\n    name=\"ocean-lib\",\n    packages=packages,\n    setup_requires=setup_requirements,\n    test_suite=\"tests\",\n    tests_require=test_requirements,\n    url=\"https://github.com/oceanprotocol/ocean.py\",\n    # fmt: off\n    # bumpversion.sh needs single-quotes\n    version='3.1.2',\n    # fmt: on\n    zip_safe=False,\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "tests/flows/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "tests/flows/conftest.py",
    "content": "from conftest_ganache import *\n"
  },
  {
    "path": "tests/flows/test_start_reuse_order_fees.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nfrom datetime import datetime, timedelta, timezone\nfrom time import sleep\nfrom typing import Tuple\n\nimport pytest\n\nfrom ocean_lib.agreements.service_types import ServiceTypes\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider\nfrom ocean_lib.example_config import get_config_dict\nfrom ocean_lib.models.data_nft import DataNFT\nfrom ocean_lib.models.datatoken_base import DatatokenBase, TokenFeeInfo\nfrom ocean_lib.models.factory_router import FactoryRouter\nfrom ocean_lib.ocean.ocean_assets import OceanAssets\nfrom ocean_lib.ocean.util import get_address_of_type, to_wei\nfrom ocean_lib.services.service import Service\nfrom ocean_lib.structures.file_objects import FilesType\nfrom ocean_lib.web3_internal.constants import MAX_UINT256\nfrom tests.resources.ddo_helpers import get_default_metadata, get_first_service_by_type\nfrom tests.resources.helper_functions import (\n    deploy_erc721_erc20,\n    get_file1,\n    get_provider_fees,\n    get_publisher_wallet,\n    get_wallet,\n    int_units,\n    transfer_bt_if_balance_lte,\n)\n\nfee_parametrisation = [\n    # Small fees\n    (0, \"Ocean\", \"5\", \"6\", \"7\"),\n    (1, \"MockDAI\", \"5\", \"6\", \"7\"),\n    (2, \"MockUSDC\", \"5\", \"6\", \"7\"),\n    # Zero fees\n    (3, \"Ocean\", \"0\", \"0\", \"0\"),\n    (4, \"MockUSDC\", \"0\", \"0\", \"0\"),\n    # Min fees\n    (\n        5,\n        \"Ocean\",\n        \"0.000000000000000001\",  # 1 wei\n        \"0.000000000000000001\",  # 1 wei\n        \"0.000000000000000001\",  # 1 wei\n    ),\n    (6, \"MockUSDC\", \"0.000001\", \"0.000001\", \"0.000001\"),  # Smallest USDC amounts\n    # Large fees\n    (7, \"Ocean\", \"500\", \"600\", \"700\"),\n    (8, \"MockUSDC\", \"500\", \"600\", \"700\"),\n]\n\n\nclass TestStartReuseOrderFees(object):\n    @classmethod\n    def setup_class(self):\n        self.ddos = []\n        self.dts = []\n        self.services = []\n        self.start_order_receipts = []\n        config = get_config_dict()\n        publisher_wallet = get_publisher_wallet()\n        file1 = get_file1()\n\n        for index in range(9):\n            data_nft = deploy_erc721_erc20(config, publisher_wallet)\n            publish_market_wallet = get_wallet(4)\n            bt = DatatokenBase.get_typed(\n                config, get_address_of_type(config, fee_parametrisation[index][1])\n            )\n\n            publish_market_order_fee = int_units(\n                fee_parametrisation[index][2], bt.decimals()\n            )\n\n            ddo, service, dt = create_asset_with_order_fee_and_timeout(\n                config=config,\n                file=file1,\n                data_nft=data_nft,\n                publisher_wallet=publisher_wallet,\n                publish_market_order_fees=TokenFeeInfo(\n                    address=publish_market_wallet.address,\n                    token=bt.address,\n                    amount=publish_market_order_fee,\n                ),\n                timeout=3600,\n                wait_for_aqua=False,\n            )\n\n            self.ddos.append(ddo)\n            self.dts.append(dt)\n            self.services.append(service)\n\n    @pytest.mark.unit\n    @pytest.mark.parametrize(\n        \"param_index, base_token_name, publish_market_order_fee_in_unit, consume_market_order_fee_in_unit, provider_fee_in_unit\",\n        fee_parametrisation,\n    )\n    def test_start_order_fees(\n        self,\n        config: dict,\n        publisher_wallet,\n        consumer_wallet,\n        provider_wallet,\n        factory_deployer_wallet,\n        factory_router: FactoryRouter,\n        param_index: int,\n        base_token_name: str,\n        publish_market_order_fee_in_unit: str,\n        consume_market_order_fee_in_unit: str,\n        provider_fee_in_unit: str,\n    ):\n        bt = DatatokenBase.get_typed(\n            config, get_address_of_type(config, base_token_name)\n        )\n        dt = self.dts[param_index]\n        publish_market_wallet = get_wallet(4)\n        consume_market_wallet = get_wallet(5)\n\n        data_provider = DataServiceProvider\n        ocean_assets = OceanAssets(config, data_provider)\n        ddo = ocean_assets._aquarius.wait_for_ddo(self.ddos[param_index].did)\n\n        publish_market_order_fee = int_units(\n            publish_market_order_fee_in_unit, bt.decimals()\n        )\n\n        # Send base tokens to the consumer so they can pay for fees\n        transfer_bt_if_balance_lte(\n            config=config,\n            bt_address=bt.address,\n            from_wallet=factory_deployer_wallet,\n            recipient=consumer_wallet.address,\n            min_balance=int_units(\"2000\", bt.decimals()),\n            amount_to_transfer=int_units(\"2000\", bt.decimals()),\n        )\n\n        # Mint 50 datatokens in consumer wallet from publisher.\n        dt.mint(\n            consumer_wallet.address,\n            to_wei(50),\n            {\"from\": publisher_wallet},\n        )\n\n        opc_collector_address = factory_router.getOPCCollector()\n\n        if base_token_name == \"Ocean\" and publish_market_order_fee_in_unit == \"500\":\n            bt.mint(\n                consumer_wallet.address,\n                int_units(\"2000\", bt.decimals()),\n                {\"from\": factory_deployer_wallet},\n            )\n\n        # Get balances\n        publisher_bt_balance_before = bt.balanceOf(publisher_wallet.address)\n        publisher_dt_balance_before = dt.balanceOf(publisher_wallet.address)\n        publish_market_bt_balance_before = bt.balanceOf(publish_market_wallet.address)\n        publish_market_dt_balance_before = dt.balanceOf(publish_market_wallet.address)\n        consume_market_bt_balance_before = bt.balanceOf(consume_market_wallet.address)\n        consume_market_dt_balance_before = dt.balanceOf(consume_market_wallet.address)\n        consumer_bt_balance_before = bt.balanceOf(consumer_wallet.address)\n        consumer_dt_balance_before = dt.balanceOf(consumer_wallet.address)\n        provider_bt_balance_before = bt.balanceOf(provider_wallet.address)\n        provider_dt_balance_before = dt.balanceOf(provider_wallet.address)\n        opc_bt_balance_before = bt.balanceOf(opc_collector_address)\n        opc_dt_balance_before = dt.balanceOf(opc_collector_address)\n\n        # Get provider fees\n        provider_fee = int_units(provider_fee_in_unit, bt.decimals())\n        valid_for_two_hours = int(\n            (datetime.now(timezone.utc) + timedelta(hours=2)).timestamp()\n        )\n        provider_fees = get_provider_fees(\n            provider_wallet,\n            bt.address,\n            provider_fee,\n            valid_for_two_hours,\n        )\n\n        # Grant datatoken infinite approval to spend consumer's base tokens\n        bt.approve(dt.address, MAX_UINT256, {\"from\": consumer_wallet})\n\n        # Start order for consumer\n        consume_market_order_fee = int_units(\n            consume_market_order_fee_in_unit, bt.decimals()\n        )\n        start_order_receipt = dt.start_order(\n            consumer=consumer_wallet.address,\n            service_index=ddo.get_index_of_service(self.services[param_index]),\n            provider_fees=provider_fees,\n            consume_market_fees=TokenFeeInfo(\n                address=consume_market_wallet.address,\n                token=bt.address,\n                amount=consume_market_order_fee,\n            ),\n            tx_dict={\"from\": consumer_wallet},\n        )\n\n        self.start_order_receipts.append(start_order_receipt)\n\n        # Get balances\n        publisher_bt_balance_after = bt.balanceOf(publisher_wallet.address)\n        publisher_dt_balance_after = dt.balanceOf(publisher_wallet.address)\n        publish_market_bt_balance_after = bt.balanceOf(publish_market_wallet.address)\n        publish_market_dt_balance_after = dt.balanceOf(publish_market_wallet.address)\n        consume_market_bt_balance_after = bt.balanceOf(consume_market_wallet.address)\n        consume_market_dt_balance_after = dt.balanceOf(consume_market_wallet.address)\n        consumer_bt_balance_after = bt.balanceOf(consumer_wallet.address)\n        consumer_dt_balance_after = dt.balanceOf(consumer_wallet.address)\n        provider_bt_balance_after = bt.balanceOf(provider_wallet.address)\n        provider_dt_balance_after = dt.balanceOf(provider_wallet.address)\n        opc_bt_balance_after = bt.balanceOf(opc_collector_address)\n        opc_dt_balance_after = dt.balanceOf(opc_collector_address)\n\n        # Get order fee amount\n        assert dt.get_publish_market_order_fees().amount == publish_market_order_fee\n\n        # Get Ocean community fee amount\n        opc_order_fee = factory_router.getOPCConsumeFee()\n        assert opc_order_fee == to_wei(0.03)\n\n        one_datatoken = to_wei(1)\n\n        # Check balances\n        assert publisher_bt_balance_before == publisher_bt_balance_after\n        assert (\n            publisher_dt_balance_before + one_datatoken - opc_order_fee\n            == publisher_dt_balance_after\n        )\n        assert (\n            publish_market_bt_balance_before + publish_market_order_fee\n            == publish_market_bt_balance_after\n        )\n        assert publish_market_dt_balance_before == publish_market_dt_balance_after\n        assert (\n            consume_market_bt_balance_before + consume_market_order_fee\n            == consume_market_bt_balance_after\n        )\n        assert consume_market_dt_balance_before == consume_market_dt_balance_after\n        assert (\n            consumer_bt_balance_before\n            - publish_market_order_fee\n            - consume_market_order_fee\n            - provider_fee\n            == consumer_bt_balance_after\n        )\n        assert consumer_dt_balance_before - one_datatoken == consumer_dt_balance_after\n        assert provider_bt_balance_before + provider_fee == provider_bt_balance_after\n        assert provider_dt_balance_before == provider_dt_balance_after\n        assert opc_bt_balance_before == opc_bt_balance_after\n        assert opc_dt_balance_before + opc_order_fee == opc_dt_balance_after\n\n    @pytest.mark.unit\n    @pytest.mark.parametrize(\n        \"param_index\",\n        range(9),\n    )\n    def test_reuse_order_fees(\n        self,\n        config: dict,\n        publisher_wallet,\n        consumer_wallet,\n        provider_wallet,\n        param_index,\n    ):\n        publish_market_wallet = get_wallet(4)\n        consume_market_wallet = get_wallet(5)\n\n        base_token_name = fee_parametrisation[param_index][1]\n        provider_fee_in_unit = fee_parametrisation[param_index][4]\n        bt = DatatokenBase.get_typed(\n            config, get_address_of_type(config, base_token_name)\n        )\n        dt = self.dts[param_index]\n        start_order_receipt = self.start_order_receipts[param_index]\n\n        # Reuse order where:\n        #     Order: valid\n        #     Provider fees: valid\n        # Simulate valid provider fees by setting them to 0\n        reuse_order_with_mock_provider_fees(\n            provider_fee_in_unit=\"0\",\n            start_order_tx_id=start_order_receipt.transactionHash,\n            bt=bt,\n            dt=dt,\n            publisher_wallet=publisher_wallet,\n            publish_market_wallet=publish_market_wallet,\n            consume_market_wallet=consume_market_wallet,\n            consumer_wallet=consumer_wallet,\n            provider_wallet=provider_wallet,\n        )\n\n        # Reuse order where:\n        #     Order: valid\n        #     Provider fees: expired\n        # Simulate expired provider fees by setting them to non-zero\n        reuse_order_with_mock_provider_fees(\n            provider_fee_in_unit=provider_fee_in_unit,\n            start_order_tx_id=start_order_receipt.transactionHash,\n            bt=bt,\n            dt=dt,\n            publisher_wallet=publisher_wallet,\n            publish_market_wallet=publish_market_wallet,\n            consume_market_wallet=consume_market_wallet,\n            consumer_wallet=consumer_wallet,\n            provider_wallet=provider_wallet,\n        )\n\n        # Sleep for 6 seconds, long enough for order to expire\n        sleep(6)\n\n        # Reuse order where:\n        #     Order: expired\n        #     Provider fees: valid\n        # Simulate valid provider fees by setting them to 0\n        reuse_order_with_mock_provider_fees(\n            provider_fee_in_unit=\"0\",\n            start_order_tx_id=start_order_receipt.transactionHash,\n            bt=bt,\n            dt=dt,\n            publisher_wallet=publisher_wallet,\n            publish_market_wallet=publish_market_wallet,\n            consume_market_wallet=consume_market_wallet,\n            consumer_wallet=consumer_wallet,\n            provider_wallet=provider_wallet,\n        )\n\n        # Reuse order where:\n        #     Order: expired\n        #     Provider fees: expired\n        # Simulate expired provider fees by setting them to non-zero\n        reuse_order_with_mock_provider_fees(\n            provider_fee_in_unit=provider_fee_in_unit,\n            start_order_tx_id=start_order_receipt.transactionHash,\n            bt=bt,\n            dt=dt,\n            publisher_wallet=publisher_wallet,\n            publish_market_wallet=publish_market_wallet,\n            consume_market_wallet=consume_market_wallet,\n            consumer_wallet=consumer_wallet,\n            provider_wallet=provider_wallet,\n        )\n\n\ndef create_asset_with_order_fee_and_timeout(\n    config: dict,\n    file: FilesType,\n    data_nft: DataNFT,\n    publisher_wallet,\n    publish_market_order_fees,\n    timeout: int,\n    wait_for_aqua: bool = True,\n) -> Tuple[DDO, Service, DatatokenBase]:\n    # Create datatoken with order fee\n    datatoken = data_nft.create_datatoken(\n        {\"from\": publisher_wallet},\n        name=\"Datatoken 1\",\n        symbol=\"DT1\",\n        publish_market_order_fees=publish_market_order_fees,\n    )\n\n    data_provider = DataServiceProvider\n    ocean_assets = OceanAssets(config, data_provider)\n    metadata = get_default_metadata()\n    files = [file]\n\n    # Create service with timeout\n    service = Service(\n        service_id=\"5\",\n        service_type=ServiceTypes.ASSET_ACCESS,\n        service_endpoint=data_provider.get_url(config),\n        datatoken=datatoken.address,\n        files=files,\n        timeout=timeout,\n    )\n\n    # Publish asset\n    data_nft, datatokens, ddo = ocean_assets.create(\n        metadata=metadata,\n        tx_dict={\"from\": publisher_wallet},\n        services=[service],\n        data_nft_address=data_nft.address,\n        deployed_datatokens=[datatoken],\n        wait_for_aqua=wait_for_aqua,\n    )\n\n    service = get_first_service_by_type(ddo, ServiceTypes.ASSET_ACCESS)\n\n    return ddo, service, datatokens[0]\n\n\ndef reuse_order_with_mock_provider_fees(\n    provider_fee_in_unit: str,\n    start_order_tx_id: str,\n    bt: DatatokenBase,\n    dt: DatatokenBase,\n    publisher_wallet,\n    publish_market_wallet,\n    consume_market_wallet,\n    consumer_wallet,\n    provider_wallet,\n):\n    \"\"\"Call reuse_order, and verify the balances/fees are correct\"\"\"\n\n    router = FactoryRouter(bt.config_dict, dt.router())\n    opc_collector_address = router.getOPCCollector()\n\n    # Get balances before reuse_order\n    publisher_bt_balance_before = bt.balanceOf(publisher_wallet.address)\n    publisher_dt_balance_before = dt.balanceOf(publisher_wallet.address)\n    publish_market_bt_balance_before = bt.balanceOf(publish_market_wallet.address)\n    publish_market_dt_balance_before = dt.balanceOf(publish_market_wallet.address)\n    consume_market_bt_balance_before = bt.balanceOf(consume_market_wallet.address)\n    consume_market_dt_balance_before = dt.balanceOf(consume_market_wallet.address)\n    consumer_bt_balance_before = bt.balanceOf(consumer_wallet.address)\n    consumer_dt_balance_before = dt.balanceOf(consumer_wallet.address)\n    provider_bt_balance_before = bt.balanceOf(provider_wallet.address)\n    provider_dt_balance_before = dt.balanceOf(provider_wallet.address)\n    opc_bt_balance_before = bt.balanceOf(opc_collector_address)\n    opc_dt_balance_before = dt.balanceOf(opc_collector_address)\n\n    # Mock provider fees\n    provider_fee = int_units(provider_fee_in_unit, bt.decimals())\n    valid_until = int((datetime.now(timezone.utc) + timedelta(seconds=10)).timestamp())\n    provider_fees = get_provider_fees(\n        provider_wallet,\n        bt.address,\n        provider_fee,\n        valid_until,\n    )\n\n    # Reuse order\n    dt.reuse_order(\n        order_tx_id=start_order_tx_id,\n        provider_fees=provider_fees,\n        tx_dict={\"from\": consumer_wallet},\n    )\n\n    # Get balances after reuse_order\n    publisher_bt_balance_after = bt.balanceOf(publisher_wallet.address)\n    publisher_dt_balance_after = dt.balanceOf(publisher_wallet.address)\n    publish_market_bt_balance_after = bt.balanceOf(publish_market_wallet.address)\n    publish_market_dt_balance_after = dt.balanceOf(publish_market_wallet.address)\n    consume_market_bt_balance_after = bt.balanceOf(consume_market_wallet.address)\n    consume_market_dt_balance_after = dt.balanceOf(consume_market_wallet.address)\n    consumer_bt_balance_after = bt.balanceOf(consumer_wallet.address)\n    consumer_dt_balance_after = dt.balanceOf(consumer_wallet.address)\n    provider_bt_balance_after = bt.balanceOf(provider_wallet.address)\n    provider_dt_balance_after = dt.balanceOf(provider_wallet.address)\n    opc_bt_balance_after = bt.balanceOf(opc_collector_address)\n    opc_dt_balance_after = dt.balanceOf(opc_collector_address)\n\n    # Check balances\n    assert publisher_bt_balance_before == publisher_bt_balance_after\n    assert publisher_dt_balance_before == publisher_dt_balance_after\n    assert publish_market_bt_balance_before == publish_market_bt_balance_after\n    assert publish_market_dt_balance_before == publish_market_dt_balance_after\n    assert consume_market_bt_balance_before == consume_market_bt_balance_after\n    assert consume_market_dt_balance_before == consume_market_dt_balance_after\n    assert consumer_bt_balance_before - provider_fee == consumer_bt_balance_after\n    assert consumer_dt_balance_before == consumer_dt_balance_after\n    assert provider_bt_balance_before + provider_fee == provider_bt_balance_after\n    assert provider_dt_balance_before == provider_dt_balance_after\n    assert opc_bt_balance_before == opc_bt_balance_after\n    assert opc_dt_balance_before == opc_dt_balance_after\n"
  },
  {
    "path": "tests/generated-readmes/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "tests/integration/ganache/conftest.py",
    "content": "from conftest_ganache import *\n"
  },
  {
    "path": "tests/integration/ganache/test_compute_flow.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport time\nfrom datetime import datetime, timedelta, timezone\nfrom typing import List, Optional\n\nimport pytest\nimport requests\nfrom attr import dataclass\n\nfrom ocean_lib.agreements.service_types import ServiceTypes\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider\nfrom ocean_lib.example_config import get_config_dict\nfrom ocean_lib.exceptions import DataProviderException\nfrom ocean_lib.models.compute_input import ComputeInput\nfrom ocean_lib.models.datatoken_base import DatatokenBase\nfrom ocean_lib.ocean.ocean import Ocean\nfrom ocean_lib.ocean.ocean_assets import OceanAssets\nfrom ocean_lib.ocean.util import to_wei\nfrom ocean_lib.structures.algorithm_metadata import AlgorithmMetadata\nfrom tests.resources.ddo_helpers import (\n    get_first_service_by_type,\n    get_registered_asset_with_compute_service,\n)\nfrom tests.resources.helper_functions import (\n    get_consumer_wallet,\n    get_publisher_ocean_instance,\n    get_publisher_wallet,\n)\n\n\nclass TestComputeFlow(object):\n    @classmethod\n    def setup_class(self):\n        publisher_wallet = get_publisher_wallet()\n        consumer_wallet = get_consumer_wallet()\n        publisher_ocean = get_publisher_ocean_instance()\n        _, _, ddo = get_registered_asset_with_compute_service(\n            publisher_ocean, publisher_wallet\n        )\n\n        self.dataset_with_compute_service = ddo\n\n        _, _, ddo = get_registered_asset_with_compute_service(\n            publisher_ocean, publisher_wallet, allow_raw_algorithms=True\n        )\n\n        self.dataset_with_compute_service_allow_raw_algo = ddo\n\n        _, _, ddo = get_registered_asset_with_compute_service(\n            publisher_ocean, publisher_wallet\n        )\n\n        self.dataset_for_update = ddo\n\n        _, _, ddo = get_registered_asset_with_compute_service(\n            publisher_ocean,\n            publisher_wallet,\n            trusted_algorithm_publishers=[publisher_wallet.address],\n        )\n\n        self.dataset_with_compute_service_and_trusted_publisher = ddo\n        self.algorithm = get_algorithm(publisher_wallet, publisher_ocean)\n        self.algorithm_with_different_publisher = get_algorithm(\n            consumer_wallet, publisher_ocean\n        )\n\n        self.published_ddos = {}\n\n    def wait_for_ddo(self, ddo):\n        if ddo.did not in self.published_ddos:\n            config = get_config_dict()\n            data_provider = DataServiceProvider\n            ocean_assets = OceanAssets(config, data_provider)\n            ddo = ocean_assets._aquarius.wait_for_ddo(ddo.did)\n            self.published_ddos[ddo.did] = ddo\n\n        return self.published_ddos[ddo.did]\n\n    @pytest.mark.integration\n    def test_compute_raw_algo(\n        self,\n        publisher_wallet,\n        publisher_ocean,\n        consumer_wallet,\n        raw_algorithm,\n    ):\n        \"\"\"Tests that a compute job with a raw algorithm starts properly.\"\"\"\n        self.wait_for_ddo(self.dataset_with_compute_service_allow_raw_algo)\n\n        run_compute_test(\n            ocean_instance=publisher_ocean,\n            publisher_wallet=publisher_wallet,\n            consumer_wallet=consumer_wallet,\n            dataset_and_userdata=AssetAndUserdata(\n                self.dataset_with_compute_service_allow_raw_algo, None\n            ),\n            algorithm_meta=raw_algorithm,\n        )\n\n        self.wait_for_ddo(self.dataset_with_compute_service)\n        with pytest.raises(DataProviderException, match=\"no_raw_algo_allowed\"):\n            run_compute_test(\n                ocean_instance=publisher_ocean,\n                publisher_wallet=publisher_wallet,\n                consumer_wallet=consumer_wallet,\n                dataset_and_userdata=AssetAndUserdata(\n                    self.dataset_with_compute_service, None\n                ),\n                algorithm_meta=raw_algorithm,\n            )\n\n    @pytest.mark.integration\n    def test_compute_registered_algo(\n        self,\n        publisher_wallet,\n        publisher_ocean,\n        consumer_wallet,\n    ):\n        \"\"\"Tests that a compute job with a registered algorithm starts properly.\"\"\"\n        self.wait_for_ddo(self.dataset_with_compute_service)\n        self.wait_for_ddo(self.algorithm)\n\n        run_compute_test(\n            ocean_instance=publisher_ocean,\n            publisher_wallet=publisher_wallet,\n            consumer_wallet=consumer_wallet,\n            dataset_and_userdata=AssetAndUserdata(\n                self.dataset_with_compute_service, None\n            ),\n            algorithm_and_userdata=AssetAndUserdata(self.algorithm, None),\n        )\n\n    @pytest.mark.integration\n    def test_compute_reuse_order(\n        self,\n        publisher_wallet,\n        publisher_ocean,\n        consumer_wallet,\n    ):\n        \"\"\"Tests that a compute job with a registered algorithm starts properly.\"\"\"\n        self.wait_for_ddo(self.dataset_with_compute_service)\n        self.wait_for_ddo(self.algorithm)\n\n        run_compute_test(\n            ocean_instance=publisher_ocean,\n            publisher_wallet=publisher_wallet,\n            consumer_wallet=consumer_wallet,\n            dataset_and_userdata=AssetAndUserdata(\n                self.dataset_with_compute_service, None\n            ),\n            algorithm_and_userdata=AssetAndUserdata(self.algorithm, None),\n            scenarios=[\"reuse_order\"],\n        )\n\n    @pytest.mark.integration\n    def test_compute_multi_inputs(\n        self,\n        publisher_wallet,\n        publisher_ocean,\n        consumer_wallet,\n        basic_asset,\n    ):\n        \"\"\"Tests that a compute job with additional Inputs (multiple assets) starts properly.\"\"\"\n        _, _, dataset_with_access_service = basic_asset\n        self.wait_for_ddo(self.dataset_with_compute_service)\n        self.wait_for_ddo(self.algorithm)\n\n        run_compute_test(\n            ocean_instance=publisher_ocean,\n            publisher_wallet=publisher_wallet,\n            consumer_wallet=consumer_wallet,\n            dataset_and_userdata=AssetAndUserdata(\n                self.dataset_with_compute_service, None\n            ),\n            algorithm_and_userdata=AssetAndUserdata(self.algorithm, None),\n            additional_datasets_and_userdata=[\n                AssetAndUserdata(\n                    dataset_with_access_service, {\"test_key\": \"test_value\"}\n                )\n            ],\n        )\n\n    @pytest.mark.integration\n    def test_compute_trusted_algorithm(self):\n        # functionality covered in test_compute_update_trusted_algorithm\n        assert True\n\n    @pytest.mark.integration\n    def test_compute_update_trusted_algorithm(\n        self,\n        publisher_wallet,\n        publisher_ocean,\n        consumer_wallet,\n    ):\n        self.wait_for_ddo(self.dataset_for_update)\n        self.wait_for_ddo(self.algorithm)\n\n        trusted_algo_list = [self.algorithm.generate_trusted_algorithms()]\n        compute_service = get_first_service_by_type(self.dataset_for_update, \"compute\")\n\n        compute_service.update_compute_values(\n            trusted_algorithms=trusted_algo_list,\n            trusted_algo_publishers=[],\n            allow_network_access=True,\n            allow_raw_algorithm=False,\n        )\n\n        updated_dataset = publisher_ocean.assets.update(\n            self.dataset_for_update, {\"from\": publisher_wallet}\n        )\n\n        # Expect to pass when trusted algorithm is used\n        run_compute_test(\n            ocean_instance=publisher_ocean,\n            publisher_wallet=publisher_wallet,\n            consumer_wallet=consumer_wallet,\n            dataset_and_userdata=AssetAndUserdata(updated_dataset, None),\n            algorithm_and_userdata=AssetAndUserdata(self.algorithm, None),\n            scenarios=[\"with_result\"],\n        )\n\n        self.wait_for_ddo(self.algorithm_with_different_publisher)\n        # Expect to fail when non-trusted algorithm is used\n        with pytest.raises(\n            DataProviderException,\n            match=\"not_trusted_algo\",\n        ):\n            run_compute_test(\n                ocean_instance=publisher_ocean,\n                publisher_wallet=publisher_wallet,\n                consumer_wallet=consumer_wallet,\n                dataset_and_userdata=AssetAndUserdata(updated_dataset, None),\n                algorithm_and_userdata=AssetAndUserdata(\n                    self.algorithm_with_different_publisher, None\n                ),\n                scenarios=[\"with_result\"],\n            )\n\n    @pytest.mark.integration\n    def test_compute_trusted_publisher(\n        self,\n        publisher_wallet,\n        publisher_ocean,\n        consumer_wallet,\n    ):\n        self.wait_for_ddo(self.dataset_with_compute_service_and_trusted_publisher)\n        self.wait_for_ddo(self.algorithm)\n\n        # Expect to pass when algorithm with trusted publisher is used\n        run_compute_test(\n            ocean_instance=publisher_ocean,\n            publisher_wallet=publisher_wallet,\n            consumer_wallet=consumer_wallet,\n            dataset_and_userdata=AssetAndUserdata(\n                self.dataset_with_compute_service_and_trusted_publisher, None\n            ),\n            algorithm_and_userdata=AssetAndUserdata(self.algorithm, None),\n        )\n\n        self.wait_for_ddo(self.algorithm_with_different_publisher)\n\n        # Expect to fail when algorithm with non-trusted publisher is used\n        with pytest.raises(DataProviderException, match=\"not_trusted_algo_publisher\"):\n            run_compute_test(\n                ocean_instance=publisher_ocean,\n                publisher_wallet=publisher_wallet,\n                consumer_wallet=consumer_wallet,\n                dataset_and_userdata=AssetAndUserdata(\n                    self.dataset_with_compute_service_and_trusted_publisher, None\n                ),\n                algorithm_and_userdata=AssetAndUserdata(\n                    self.algorithm_with_different_publisher, None\n                ),\n            )\n\n    @pytest.mark.integration\n    def test_compute_just_provider_fees(\n        self,\n        publisher_wallet,\n        publisher_ocean,\n        consumer_wallet,\n    ):\n        self.wait_for_ddo(self.dataset_with_compute_service)\n        self.wait_for_ddo(self.algorithm)\n\n        \"\"\"Tests that the correct compute provider fees are calculated.\"\"\"\n        run_compute_test(\n            ocean_instance=publisher_ocean,\n            publisher_wallet=publisher_wallet,\n            consumer_wallet=consumer_wallet,\n            dataset_and_userdata=AssetAndUserdata(\n                self.dataset_with_compute_service, None\n            ),\n            algorithm_and_userdata=AssetAndUserdata(self.algorithm, None),\n            scenarios=[\"just_fees\"],\n        )\n\n\ndef get_algorithm(wallet, ocean_instance):\n    # Setup algorithm meta to run raw algorithm\n    _, _, ddo = ocean_instance.assets.create_algo_asset(\n        name=\"Sample asset\",\n        url=\"https://raw.githubusercontent.com/oceanprotocol/c2d-examples/main/branin_and_gpr/gpr.py\",\n        tx_dict={\"from\": wallet},\n        image=\"oceanprotocol/algo_dockers\",\n        tag=\"python-branin\",\n        checksum=\"sha256:8221d20c1c16491d7d56b9657ea09082c0ee4a8ab1a6621fa720da58b09580e4\",\n        wait_for_aqua=False,\n    )\n    # verify the asset is available in Aquarius\n    ocean_instance.assets.resolve(ddo.did)\n    return ddo\n\n\n@pytest.fixture\ndef raw_algorithm():\n    req = requests.get(\n        \"https://raw.githubusercontent.com/oceanprotocol/test-algorithm/master/javascript/algo.js\"\n    )\n    return AlgorithmMetadata(\n        {\n            \"rawcode\": req.text,\n            \"language\": \"Node.js\",\n            \"format\": \"docker-image\",\n            \"version\": \"0.1\",\n            \"container\": {\n                \"entrypoint\": \"python $ALGO\",\n                \"image\": \"oceanprotocol/algo_dockers\",\n                \"tag\": \"python-branin\",\n                \"checksum\": \"sha256:8221d20c1c16491d7d56b9657ea09082c0ee4a8ab1a6621fa720da58b09580e4\",\n            },\n        }\n    )\n\n\n@dataclass\nclass AssetAndUserdata:\n    ddo: DDO\n    userdata: Optional[dict]\n\n\ndef _mint_and_build_compute_input(\n    dataset_and_userdata: AssetAndUserdata,\n    service_type: str,\n    publisher_wallet,\n    consumer_wallet,\n    ocean_instance: Ocean,\n) -> ComputeInput:\n    service = get_first_service_by_type(dataset_and_userdata.ddo, service_type)\n    datatoken = DatatokenBase.get_typed(ocean_instance.config_dict, service.datatoken)\n    minter = (\n        consumer_wallet\n        if datatoken.isMinter(consumer_wallet.address)\n        else publisher_wallet\n    )\n    datatoken.mint(consumer_wallet.address, to_wei(10), {\"from\": minter})\n\n    return ComputeInput(\n        dataset_and_userdata.ddo,\n        service,\n        userdata=dataset_and_userdata.userdata,\n        consume_market_order_fee_token=datatoken.address,\n        consume_market_order_fee_amount=0,\n    )\n\n\ndef run_compute_test(\n    ocean_instance: Ocean,\n    publisher_wallet,\n    consumer_wallet,\n    dataset_and_userdata: AssetAndUserdata,\n    algorithm_and_userdata: Optional[AssetAndUserdata] = None,\n    algorithm_meta: Optional[AlgorithmMetadata] = None,\n    algorithm_algocustomdata: Optional[dict] = None,\n    additional_datasets_and_userdata: List[AssetAndUserdata] = [],\n    scenarios: Optional[List[str]] = None,\n):\n    \"\"\"Helper function to bootstrap compute job creation and status checking.\"\"\"\n    assert (\n        algorithm_and_userdata or algorithm_meta\n    ), \"either algorithm_and_userdata or algorithm_meta must be provided.\"\n\n    if not scenarios:\n        scenarios = []\n\n    datasets = [\n        _mint_and_build_compute_input(\n            dataset_and_userdata,\n            ServiceTypes.CLOUD_COMPUTE,\n            publisher_wallet,\n            consumer_wallet,\n            ocean_instance,\n        )\n    ]\n\n    # build additional datasets\n    for asset_and_userdata in additional_datasets_and_userdata:\n        service_type = ServiceTypes.ASSET_ACCESS\n        if not get_first_service_by_type(asset_and_userdata.ddo, service_type):\n            service_type = ServiceTypes.CLOUD_COMPUTE\n\n        datasets.append(\n            _mint_and_build_compute_input(\n                asset_and_userdata,\n                service_type,\n                publisher_wallet,\n                consumer_wallet,\n                ocean_instance,\n            )\n        )\n\n    # Order algo download service (aka. access service)\n    algorithm = None\n    if algorithm_and_userdata:\n        algorithm = _mint_and_build_compute_input(\n            algorithm_and_userdata,\n            ServiceTypes.ASSET_ACCESS,\n            publisher_wallet,\n            consumer_wallet,\n            ocean_instance,\n        )\n\n    service = get_first_service_by_type(\n        dataset_and_userdata.ddo, ServiceTypes.CLOUD_COMPUTE\n    )\n\n    try:\n        free_c2d_env = ocean_instance.compute.get_free_c2d_environment(\n            service.service_endpoint, 8996\n        )\n    except StopIteration:\n        assert False, \"No free c2d environment found.\"\n\n    time_difference = (\n        timedelta(hours=1) if \"reuse_order\" not in scenarios else timedelta(seconds=30)\n    )\n    valid_until = int((datetime.now(timezone.utc) + time_difference).timestamp())\n\n    if \"just_fees\" in scenarios:\n        fees_response = ocean_instance.retrieve_provider_fees_for_compute(\n            datasets,\n            algorithm if algorithm else algorithm_meta,\n            consumer_address=free_c2d_env[\"consumerAddress\"],\n            compute_environment=free_c2d_env[\"id\"],\n            valid_until=valid_until,\n        )\n\n        assert \"algorithm\" in fees_response\n        assert len(fees_response[\"datasets\"]) == 1\n\n        return\n\n    datasets, algorithm = ocean_instance.assets.pay_for_compute_service(\n        datasets,\n        algorithm if algorithm else algorithm_meta,\n        consumer_address=free_c2d_env[\"consumerAddress\"],\n        compute_environment=free_c2d_env[\"id\"],\n        valid_until=valid_until,\n        consume_market_order_fee_address=consumer_wallet.address,\n        tx_dict={\"from\": consumer_wallet},\n    )\n\n    # Start compute job\n    job_id = ocean_instance.compute.start(\n        consumer_wallet,\n        datasets[0],\n        free_c2d_env[\"id\"],\n        algorithm,\n        algorithm_meta,\n        algorithm_algocustomdata,\n        datasets[1:],\n    )\n\n    status = ocean_instance.compute.status(\n        dataset_and_userdata.ddo, service, job_id, consumer_wallet\n    )\n    print(f\"got job status: {status}\")\n\n    assert (\n        status and status[\"ok\"]\n    ), f\"something not right about the compute job, got status: {status}\"\n\n    status = ocean_instance.compute.stop(\n        dataset_and_userdata.ddo, service, job_id, consumer_wallet\n    )\n    print(f\"got job status after requesting stop: {status}\")\n    assert status, f\"something not right about the compute job, got status: {status}\"\n\n    if \"with_result\" in scenarios:\n        succeeded = False\n        for _ in range(0, 300):\n            status = ocean_instance.compute.status(\n                dataset_and_userdata.ddo, service, job_id, consumer_wallet\n            )\n            # wait until job is done, see:\n            # https://github.com/oceanprotocol/operator-service/blob/main/API.md#status-description\n            if status[\"status\"] > 60:\n                succeeded = True\n                break\n            time.sleep(5)\n\n        print(f\"got status: {status}\")\n        assert succeeded, \"compute job unsuccessful\"\n\n        log_file = ocean_instance.compute.compute_job_result_logs(\n            dataset_and_userdata.ddo, service, job_id, consumer_wallet, \"algorithmLog\"\n        )\n        print(f\"got algo log file: {str(log_file)}\")\n\n        _ = ocean_instance.compute.result(\n            dataset_and_userdata.ddo, service, job_id, 0, consumer_wallet\n        )\n\n        prev_dt_tx_id = datasets[0].transfer_tx_id\n        prev_algo_tx_id = algorithm.transfer_tx_id\n\n        # retry initialize but all orders are already valid\n        datasets, algorithm = ocean_instance.assets.pay_for_compute_service(\n            datasets,\n            algorithm if algorithm else algorithm_meta,\n            consumer_address=free_c2d_env[\"consumerAddress\"],\n            compute_environment=free_c2d_env[\"id\"],\n            valid_until=valid_until,\n            consume_market_order_fee_address=consumer_wallet.address,\n            tx_dict={\"from\": consumer_wallet},\n        )\n\n        # transferTxId was not updated\n        assert datasets[0].transfer_tx_id == prev_dt_tx_id\n        assert algorithm.transfer_tx_id == prev_algo_tx_id\n\n    if \"reuse_order\" in scenarios:\n        prev_dt_tx_id = datasets[0].transfer_tx_id\n        prev_algo_tx_id = algorithm.transfer_tx_id\n        # ensure order expires\n        time.sleep(time_difference.seconds + 1)\n\n        valid_until = int((datetime.now(timezone.utc) + time_difference).timestamp())\n        datasets, algorithm = ocean_instance.assets.pay_for_compute_service(\n            datasets,\n            algorithm if algorithm else algorithm_meta,\n            consumer_address=free_c2d_env[\"consumerAddress\"],\n            compute_environment=free_c2d_env[\"id\"],\n            valid_until=valid_until,\n            consume_market_order_fee_address=consumer_wallet.address,\n            tx_dict={\"from\": consumer_wallet},\n        )\n\n        assert datasets[0].transfer_tx_id != prev_dt_tx_id\n        assert algorithm.transfer_tx_id != prev_algo_tx_id\n\n        job_id = ocean_instance.compute.start(\n            consumer_wallet,\n            datasets[0],\n            free_c2d_env[\"id\"],\n            algorithm,\n            algorithm_meta,\n            algorithm_algocustomdata,\n            datasets[1:],\n        )\n\n        assert job_id, \"can not reuse order\"\n"
  },
  {
    "path": "tests/integration/ganache/test_consume_flow.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport os\nimport shutil\n\nimport pytest\n\nfrom ocean_lib.agreements.service_types import ServiceTypes\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider\nfrom ocean_lib.ocean.ocean_assets import OceanAssets\nfrom ocean_lib.ocean.util import get_address_of_type, to_wei\nfrom tests.resources.ddo_helpers import get_first_service_by_type\n\nARWEAVE_TRANSACTION_ID = \"a4qJoQZa1poIv5guEzkfgZYSAD0uYm7Vw4zm_tCswVQ\"\n\n\n@pytest.mark.integration\n@pytest.mark.parametrize(\"asset_type\", [\"simple\", \"graphql\", \"onchain\", \"arweave\"])\ndef test_consume_asset(\n    config: dict, publisher_wallet, consumer_wallet, basic_asset, asset_type\n):\n    data_provider = DataServiceProvider\n    ocean_assets = OceanAssets(config, data_provider)\n\n    if asset_type == \"simple\":\n        data_nft, dt, ddo = basic_asset\n    elif asset_type == \"arweave\":\n        data_nft, dt, ddo = ocean_assets.create_arweave_asset(\n            \"Data NFTs in Ocean\", ARWEAVE_TRANSACTION_ID, {\"from\": publisher_wallet}\n        )\n    elif asset_type == \"onchain\":\n        abi = {\n            \"inputs\": [],\n            \"name\": \"swapOceanFee\",\n            \"outputs\": [{\"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\"}],\n            \"stateMutability\": \"view\",\n            \"type\": \"function\",\n        }\n        router_address = get_address_of_type(config, \"Router\")\n\n        data_nft, dt, ddo = ocean_assets.create_onchain_asset(\n            \"Data NFTs in Ocean\", router_address, abi, {\"from\": publisher_wallet}\n        )\n    else:\n        url = \"http://172.15.0.15:8030/graphql\"\n        query = \"\"\"\n                query{\n                    indexingStatuses{\n                        subgraph\n                        chains\n                        node\n                    }\n                }\n                \"\"\"\n\n        data_nft, dt, ddo = ocean_assets.create_graphql_asset(\n            \"Data NFTs in Ocean\", url, query, {\"from\": publisher_wallet}\n        )\n\n    assert ddo, \"The ddo is not created.\"\n    assert ddo.nft[\"address\"] == data_nft.address\n    assert ddo.nft[\"owner\"] == publisher_wallet.address\n    if asset_type != \"simple\":\n        assert ddo.datatokens[0][\"name\"] == \"Data NFTs in Ocean: DT1\"\n\n    service = get_first_service_by_type(ddo, ServiceTypes.ASSET_ACCESS)\n\n    # Mint 50 datatokens in consumer wallet from publisher. Max cap = 100\n    dt.mint(\n        consumer_wallet.address,\n        to_wei(50),\n        {\"from\": publisher_wallet},\n    )\n\n    # Initialize service\n    response = data_provider.initialize(\n        did=ddo.did, service=service, consumer_address=consumer_wallet.address\n    )\n    assert response\n    assert response.status_code == 200\n    assert response.json()[\"providerFee\"]\n    provider_fees = response.json()[\"providerFee\"]\n\n    # Start order for consumer\n    receipt = dt.start_order(\n        consumer=consumer_wallet.address,\n        service_index=ddo.get_index_of_service(service),\n        provider_fees=provider_fees,\n        tx_dict={\"from\": consumer_wallet},\n    )\n\n    # Download file\n    destination = config[\"DOWNLOADS_PATH\"]\n    if not os.path.isabs(destination):\n        destination = os.path.abspath(destination)\n\n    if os.path.exists(destination) and len(os.listdir(destination)) > 0:\n        list(\n            map(\n                lambda d: shutil.rmtree(os.path.join(destination, d)),\n                os.listdir(destination),\n            )\n        )\n\n    if not os.path.exists(destination):\n        os.makedirs(destination)\n\n    assert len(os.listdir(destination)) == 0\n\n    ocean_assets.download_asset(\n        ddo,\n        consumer_wallet,\n        destination,\n        receipt.transactionHash.hex(),\n        service,\n    )\n\n    assert (\n        len(os.listdir(os.path.join(destination, os.listdir(destination)[0]))) == 1\n    ), \"The asset folder is empty.\"\n"
  },
  {
    "path": "tests/integration/ganache/test_disconnecting_components.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport threading\nimport time\n\nimport pytest\nimport requests\n\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.data_provider.data_encryptor import DataEncryptor\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider\nfrom ocean_lib.example_config import DEFAULT_PROVIDER_URL\nfrom ocean_lib.ocean.ocean import Ocean\n\nexception_flag = 0\n\n\ndef test_with_wrong_provider(config, caplog):\n    \"\"\"Tests encrypt with a good provider URL and then switch to a bad one.\"\"\"\n\n    config[\"PROVIDER_URL\"] = DEFAULT_PROVIDER_URL\n    updating_thread = threading.Thread(\n        target=_update_with_wrong_component,\n        args=(config,),\n    )\n    updating_thread.start()\n    _iterative_encrypt(config)\n    updating_thread.join()\n\n    assert \"Asset urls encrypted successfully\" in caplog.text\n    assert exception_flag == 1\n\n\ndef test_with_wrong_aquarius(publisher_wallet, caplog, monkeypatch, config):\n    \"\"\"Tests DDO creation with a good config.ini and then switch to a bad one.\"\"\"\n    config[\"METADATA_CACHE_URI\"] = \"http:://not-valid-aqua.com\"\n\n    with pytest.raises(Exception, match=\"Invalid or unresponsive aquarius url\"):\n        ocean = Ocean(config, DataServiceProvider)\n\n    config[\"METADATA_CACHE_URI\"] = \"http://172.15.0.5:5000\"\n    ocean = Ocean(config, DataServiceProvider)\n\n    # force a bad URL, assuming initial Ocean and Aquarius objects were created successfully\n    ocean.assets._aquarius.base_url = \"http://not-valid-aqua.com\"\n    with pytest.raises(Exception):\n        ocean.assets._aquarius.validate_ddo(DDO())\n\n\ndef _create_ddo(ocean, publisher):\n    global exception_flag\n    time.sleep(5)\n    try:\n        ocean.assets.create_url_asset(\n            \"Sample asset\", \"https://foo.txt\", {\"from\": publisher}\n        )\n    except requests.exceptions.InvalidURL as err:\n        exception_flag = 1\n        assert err.args[0] == \"InvalidURL http://foourl.com.\"\n    except requests.exceptions.ConnectionError as e:\n        exception_flag = 2\n        assert (\n            e.args[0]\n            .args[0]\n            .startswith(\"HTTPConnectionPool(host='fooaqua.com', port=80)\")\n        )\n\n\ndef _iterative_create_ddo(mock_ocean, publisher):\n    time.sleep(10)\n    _create_ddo(mock_ocean.return_value, publisher)\n\n\ndef _iterative_encrypt(mock):\n    global exception_flag\n    for _ in range(5):\n        try:\n            DataEncryptor.encrypt({}, mock[\"PROVIDER_URL\"], 8996)\n        except requests.exceptions.InvalidURL as err:\n            exception_flag = 1\n            assert err.args[0] == \"InvalidURL http://foourl.com.\"\n        time.sleep(1)\n\n\ndef _update_with_wrong_component(mock):\n    time.sleep(2)\n    mock[\"PROVIDER_URL\"] = \"http://foourl.com\"\n"
  },
  {
    "path": "tests/integration/ganache/test_graphql.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport json\nimport os\nimport shutil\n\nimport pytest\n\nfrom ocean_lib.agreements.service_types import ServiceTypes\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider\nfrom ocean_lib.ocean.ocean import Ocean\nfrom ocean_lib.ocean.util import to_wei\nfrom tests.resources.ddo_helpers import get_first_service_by_type\n\n\n@pytest.mark.integration\ndef test_consume_simple_graphql_query(\n    config: dict,\n    publisher_wallet,\n    consumer_wallet,\n):\n    data_provider = DataServiceProvider\n    ocean = Ocean(config)\n    url = \"http://172.15.0.15:8030/graphql\"\n    query = \"\"\"\n        query{\n            indexingStatuses{\n                subgraph\n                chains\n                node\n            }\n        }\n        \"\"\"\n\n    data_nft, dt, ddo = ocean.assets.create_graphql_asset(\n        \"Data NFTs in Ocean\", url, query, {\"from\": publisher_wallet}\n    )\n\n    assert ddo, \"The ddo is not created.\"\n    assert ddo.nft[\"address\"] == data_nft.address\n    assert ddo.nft[\"owner\"] == publisher_wallet.address\n    assert ddo.datatokens[0][\"name\"] == \"Data NFTs in Ocean: DT1\"\n\n    service = get_first_service_by_type(ddo, ServiceTypes.ASSET_ACCESS)\n\n    # Mint 50 datatokens in consumer wallet from publisher. Max cap = 100\n    dt.mint(\n        consumer_wallet.address,\n        to_wei(50),\n        {\"from\": publisher_wallet},\n    )\n\n    # Initialize service\n    response = data_provider.initialize(\n        did=ddo.did, service=service, consumer_address=consumer_wallet.address\n    )\n    assert response\n    assert response.status_code == 200\n    assert response.json()[\"providerFee\"]\n    provider_fees = response.json()[\"providerFee\"]\n\n    # Start order for consumer\n    receipt = dt.start_order(\n        consumer=consumer_wallet.address,\n        service_index=ddo.get_index_of_service(service),\n        provider_fees=provider_fees,\n        tx_dict={\"from\": consumer_wallet},\n    )\n\n    # Download file\n    destination = config[\"DOWNLOADS_PATH\"]\n    if not os.path.isabs(destination):\n        destination = os.path.abspath(destination)\n\n    if os.path.exists(destination) and len(os.listdir(destination)) > 0:\n        list(\n            map(\n                lambda d: shutil.rmtree(os.path.join(destination, d)),\n                os.listdir(destination),\n            )\n        )\n\n    if not os.path.exists(destination):\n        os.makedirs(destination)\n\n    assert len(os.listdir(destination)) == 0\n\n    ocean.assets.download_asset(\n        ddo,\n        consumer_wallet,\n        destination,\n        receipt.transactionHash.hex(),\n        service,\n    )\n    file_path = os.path.join(destination, os.listdir(destination)[0])\n    assert len(os.listdir(file_path)) == 1, \"The asset folder is empty.\"\n\n    with open(os.path.join(file_path, os.listdir(file_path)[0])) as f:\n        contents = f.readlines()\n        content = json.loads(contents[0])\n        assert \"data\" in content.keys()\n        assert \"indexingStatuses\" in content[\"data\"].keys()\n"
  },
  {
    "path": "tests/integration/ganache/test_market_flow.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\nimport os\n\nimport pytest\nfrom web3.main import Web3\n\nfrom ocean_lib.models.datatoken_base import TokenFeeInfo\nfrom ocean_lib.ocean.util import to_wei\nfrom tests.resources.helper_functions import get_another_consumer_ocean_instance\n\n\n@pytest.mark.integration\n@pytest.mark.parametrize(\"consumer_type\", [\"publisher\", \"another_user\"])\ndef test_market_flow(\n    publisher_wallet,\n    consumer_wallet,\n    basic_asset,\n    consumer_ocean,\n    another_consumer_wallet,\n    consumer_type,\n):\n    \"\"\"Tests that an order is correctly placed on the market.\n\n    The parameter implicit_none sends the payload with an empty key as the delegated consumer.\n    The parameter explicit_none sends None as the delegated consumer, explicitly.\"\"\"\n    consumer_ocean = consumer_ocean\n    another_consumer_ocean = get_another_consumer_ocean_instance(use_provider_mock=True)\n\n    data_nft, datatoken, ddo = basic_asset\n    service = ddo.services[0]\n\n    # Mint data tokens and assign to publisher\n    datatoken.mint(\n        publisher_wallet.address,\n        to_wei(50),\n        {\"from\": publisher_wallet},\n    )\n\n    # Give the consumer some datatokens so they can order the service\n    datatoken.transfer(consumer_wallet.address, to_wei(10), {\"from\": publisher_wallet})\n\n    # Place order for the download service\n    if consumer_type == \"publisher\":\n        order_tx_id = consumer_ocean.assets.pay_for_access_service(\n            ddo,\n            {\"from\": consumer_wallet},\n            service=service,\n            consume_market_fees=TokenFeeInfo(token=datatoken.address),\n        ).hex()\n        asset_folder = consumer_ocean.assets.download_asset(\n            ddo,\n            consumer_wallet,\n            consumer_ocean.config_dict[\"DOWNLOADS_PATH\"],\n            order_tx_id,\n            service,\n        )\n    else:\n        order_tx_id = consumer_ocean.assets.pay_for_access_service(\n            ddo,\n            {\"from\": consumer_wallet},\n            service=service,\n            consume_market_fees=TokenFeeInfo(\n                address=another_consumer_wallet.address,\n                token=datatoken.address,\n            ),\n            consumer_address=another_consumer_wallet.address,\n        ).hex()\n        asset_folder = consumer_ocean.assets.download_asset(\n            ddo,\n            another_consumer_wallet,\n            another_consumer_ocean.config_dict[\"DOWNLOADS_PATH\"],\n            order_tx_id,\n            service,\n        )\n\n    assert len(os.listdir(asset_folder)) >= 1, \"The asset folder is empty.\"\n\n    orders = consumer_ocean.get_user_orders(consumer_wallet.address, datatoken.address)\n    assert (\n        orders\n    ), f\"no orders found using the order history: datatoken {datatoken.address}, consumer {consumer_wallet.address}\"\n\n    orders = consumer_ocean.get_user_orders(\n        consumer_wallet.address,\n        Web3.to_checksum_address(datatoken.address),\n    )\n    assert (\n        orders\n    ), f\"no orders found using the order history: datatoken {datatoken.address}, consumer {consumer_wallet.address}\"\n\n\n@pytest.mark.integration\ndef test_pay_for_access_service_good_default(\n    basic_asset,\n    publisher_wallet,\n    consumer_wallet,\n    consumer_ocean,\n):\n    data_nft, datatoken, ddo = basic_asset\n    service = ddo.services[0]\n\n    # Mint datatokens to consumer\n    datatoken.mint(consumer_wallet.address, to_wei(50), {\"from\": publisher_wallet})\n\n    # Place order for the download service\n    # - Here, use good defaults for service, and fee-related args\n    order_tx_id = consumer_ocean.assets.pay_for_access_service(\n        ddo, {\"from\": consumer_wallet}\n    ).hex()\n\n    asset_folder = consumer_ocean.assets.download_asset(\n        ddo,\n        consumer_wallet,\n        consumer_ocean.config_dict[\"DOWNLOADS_PATH\"],\n        order_tx_id,\n        service,\n    )\n\n    # basic check. Leave thorough checks to other tests here\n    assert len(os.listdir(asset_folder)) >= 1, \"The asset folder is empty.\"\n"
  },
  {
    "path": "tests/integration/ganache/test_onchain.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport os\nimport shutil\n\nimport pytest\n\nfrom ocean_lib.agreements.service_types import ServiceTypes\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider\nfrom ocean_lib.models.datatoken_base import DatatokenArguments, DatatokenBase\nfrom ocean_lib.ocean.ocean_assets import OceanAssets\nfrom ocean_lib.ocean.util import get_address_of_type, to_wei\nfrom ocean_lib.structures.file_objects import SmartContractCall\nfrom tests.resources.ddo_helpers import get_first_service_by_type\n\n\n@pytest.mark.integration\ndef test_consume_parametrized_onchain_data(\n    config: dict,\n    publisher_wallet,\n    consumer_wallet,\n):\n    data_provider = DataServiceProvider\n    ocean_assets = OceanAssets(config, data_provider)\n    metadata = {\n        \"created\": \"2020-11-15T12:27:48Z\",\n        \"updated\": \"2021-05-17T21:58:02Z\",\n        \"description\": \"Sample description\",\n        \"name\": \"Sample asset\",\n        \"type\": \"dataset\",\n        \"author\": \"OPF\",\n        \"license\": \"https://market.oceanprotocol.com/terms\",\n    }\n    abi = {\n        \"inputs\": [{\"internalType\": \"address\", \"name\": \"baseToken\", \"type\": \"address\"}],\n        \"name\": \"getOPCFee\",\n        \"outputs\": [{\"internalType\": \"uint256\", \"name\": \"\", \"type\": \"uint256\"}],\n        \"stateMutability\": \"view\",\n        \"type\": \"function\",\n    }\n    router_address = get_address_of_type(config, \"Router\")\n    onchain_data = SmartContractCall(\n        address=router_address,\n        chainId=config[\"web3_instance\"].eth.chain_id,\n        abi=abi,\n    )\n\n    files = [onchain_data]\n\n    # to consume dataset, consumer needs to send a value for nftAddress\n    consumer_parameters = [\n        {\n            \"name\": \"baseToken\",\n            \"type\": \"text\",\n            \"label\": \"baseToken\",\n            \"required\": True,\n            \"description\": \"baseToken to check for fee\",\n            \"default\": \"0x0000000000000000000000000000000000000000\",\n        }\n    ]\n\n    # Publish a plain asset with one data token on chain\n    dt_arg = DatatokenArguments(files=files, consumer_parameters=consumer_parameters)\n    data_nft, _, ddo = ocean_assets.create(\n        metadata=metadata,\n        tx_dict={\"from\": publisher_wallet},\n        datatoken_args=[dt_arg],\n    )\n\n    assert ddo, \"The ddo is not created.\"\n    assert ddo.nft[\"name\"] == \"Sample asset\"\n    assert ddo.nft[\"symbol\"] == \"Sample asset\"\n    assert ddo.nft[\"address\"] == data_nft.address\n    assert ddo.nft[\"owner\"] == publisher_wallet.address\n    assert ddo.datatokens[0][\"name\"] == \"Datatoken 1\"\n    assert ddo.datatokens[0][\"symbol\"] == \"DT1\"\n\n    service = get_first_service_by_type(ddo, ServiceTypes.ASSET_ACCESS)\n    dt = DatatokenBase.get_typed(config, ddo.datatokens[0][\"address\"])\n\n    # Mint 50 datatokens in consumer wallet from publisher. Max cap = 100\n    dt.mint(\n        consumer_wallet.address,\n        to_wei(50),\n        {\"from\": publisher_wallet},\n    )\n\n    # Initialize service\n    response = data_provider.initialize(\n        did=ddo.did, service=service, consumer_address=consumer_wallet.address\n    )\n    assert response\n    assert response.status_code == 200\n    assert response.json()[\"providerFee\"]\n    provider_fees = response.json()[\"providerFee\"]\n\n    # Start order for consumer\n    receipt = dt.start_order(\n        consumer=consumer_wallet.address,\n        service_index=ddo.get_index_of_service(service),\n        provider_fees=provider_fees,\n        tx_dict={\"from\": consumer_wallet},\n    )\n\n    # Download file\n    destination = config[\"DOWNLOADS_PATH\"]\n    if not os.path.isabs(destination):\n        destination = os.path.abspath(destination)\n\n    if os.path.exists(destination) and len(os.listdir(destination)) > 0:\n        list(\n            map(\n                lambda d: shutil.rmtree(os.path.join(destination, d)),\n                os.listdir(destination),\n            )\n        )\n\n    if not os.path.exists(destination):\n        os.makedirs(destination)\n\n    assert len(os.listdir(destination)) == 0\n\n    # this is where user is sending the required consumer_parameters\n    userdata = {\"baseToken\": ddo.nft_address.lower()}\n    ocean_assets.download_asset(\n        ddo,\n        consumer_wallet,\n        destination,\n        receipt.transactionHash.hex(),\n        service,\n        userdata=userdata,\n    )\n\n    dir_files = os.listdir(os.path.join(destination, os.listdir(destination)[0]))\n    assert len(dir_files) == len(files), \"The asset folder is empty.\"\n"
  },
  {
    "path": "tests/integration/remote/__init__.py",
    "content": ""
  },
  {
    "path": "tests/integration/remote/test_mumbai_main.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport os\n\nfrom ocean_lib.example_config import get_config_dict\nfrom ocean_lib.ocean.ocean import Ocean\n\nfrom . import util\n\n\ndef _get_mumbai_rpc():\n    infura_id = os.getenv(\"WEB3_INFURA_PROJECT_ID\")\n\n    if not infura_id:\n        return \"https://rpc-mumbai.maticvigil.com\"\n\n    return f\"https://polygon-mumbai.infura.io/v3/{infura_id}\"\n\n\ndef test_nonocean_tx(tmp_path, monkeypatch):\n    \"\"\"Do a simple non-Ocean tx on Mumbai. Only use Ocean config\"\"\"\n    monkeypatch.delenv(\"ADDRESS_FILE\")\n    # setup\n\n    config = get_config_dict(_get_mumbai_rpc())\n    ocean = Ocean(config)\n    (alice_wallet, bob_wallet) = util.get_wallets()\n\n    # Do a simple-as-possible test that uses ocean stack, while accounting for gotchas\n    util.do_nonocean_tx_and_handle_gotchas(ocean, alice_wallet, bob_wallet)\n\n\ndef test_ocean_tx__create(tmp_path, monkeypatch):\n    \"\"\"On Mumbai, do a simple Ocean tx: create\"\"\"\n    monkeypatch.delenv(\"ADDRESS_FILE\")\n    # setup\n\n    config = get_config_dict(_get_mumbai_rpc())\n    ocean = Ocean(config)\n\n    (alice_wallet, _) = util.get_wallets()\n\n    # Do a simple-as-possible test that uses ocean stack, while accounting for gotchas\n    util.do_ocean_tx_and_handle_gotchas(ocean, alice_wallet)\n"
  },
  {
    "path": "tests/integration/remote/test_mumbai_readme.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pathlib\nimport runpy\n\nfrom . import util\n\n\ndef test_simple_remote_readme(monkeypatch):\n    monkeypatch.delenv(\"ADDRESS_FILE\")\n    (ref_alice_wallet, _) = util.get_wallets()\n\n    # README generation command:\n    # mkcodes --github --output tests/generated-readmes/test_{name}.{ext} READMEs\n    script = pathlib.Path(\n        __file__, \"..\", \"..\", \"..\", \"generated-readmes\", \"test_setup-remote.py\"\n    )\n\n    try:\n        result = runpy.run_path(str(script), run_name=\"__main__\")\n    except AssertionError as e:  # skip if zero funds in account\n        if \"Alice needs MATIC\" in str(e) or \"Bob needs MATIC\" in str(e):\n            return\n        raise (e)\n\n    ocean = result[\"ocean\"]\n    alice = result[\"alice\"]\n\n    # at this point, this script should have set up ocean and the wallets\n\n    # make sure that the script used REMOTE_TEST_PRIVATE_KEY1 wallet, like reference wallet\n    assert alice.address == ref_alice_wallet.address\n\n    # besides what the readme script does, is it actually able to do more?\n    util.do_ocean_tx_and_handle_gotchas(ocean, alice)\n"
  },
  {
    "path": "tests/integration/remote/test_polygon.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport os\n\nimport pytest\n\nfrom ocean_lib.example_config import get_config_dict\nfrom ocean_lib.ocean.ocean import Ocean\n\nfrom . import util\n\n\ndef _get_polygon_rpc():\n    infura_id = os.getenv(\"WEB3_INFURA_PROJECT_ID\")\n\n    if not infura_id:\n        return \"https://polygon-rpc.com\"\n\n    return f\"https://polygon-mainnet.infura.io/v3/{infura_id}\"\n\n\n@pytest.mark.integration\ndef test_ocean_tx__create(tmp_path, monkeypatch):\n    \"\"\"On Polygon, do a simple Ocean tx: create\"\"\"\n    monkeypatch.delenv(\"ADDRESS_FILE\")\n\n    # setup\n    config = get_config_dict(_get_polygon_rpc())\n    ocean = Ocean(config)\n\n    (alice_wallet, _) = util.get_wallets()\n\n    # Do a simple-as-possible test that uses ocean stack, while accounting for gotchas\n    util.do_ocean_tx_and_handle_gotchas(ocean, alice_wallet)\n"
  },
  {
    "path": "tests/integration/remote/util.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport os\nimport random\nimport string\nimport time\nimport warnings\n\nfrom enforce_typing import enforce_types\nfrom eth_account import Account\n\nfrom ocean_lib.ocean.util import send_ether, to_wei\nfrom ocean_lib.web3_internal.utils import get_gas_fees\n\n\n@enforce_types\ndef remote_config_mumbai(tmp_path):\n    config = {\n        \"NETWORK_NAME\": \"mumbai\",\n        \"METADATA_CACHE_URI\": \"https://v4.aquarius.oceanprotocol.com\",\n        \"PROVIDER_URL\": \"https://v4.provider.mumbai.oceanprotocol.com\",\n        \"DOWNLOADS_PATH\": \"consume-downloads\",\n    }\n\n    return config\n\n\n@enforce_types\ndef remote_config_polygon(tmp_path):\n    config = {\n        \"NETWORK_NAME\": \"polygon\",\n        \"METADATA_CACHE_URI\": \"https://v4.aquarius.oceanprotocol.com\",\n        \"PROVIDER_URL\": \"https://v4.provider.polygon.oceanprotocol.com\",\n        \"DOWNLOADS_PATH\": \"consume-downloads\",\n    }\n\n    return config\n\n\n@enforce_types\ndef get_wallets():\n    alice_private_key = os.getenv(\"REMOTE_TEST_PRIVATE_KEY1\")\n    bob_private_key = os.getenv(\"REMOTE_TEST_PRIVATE_KEY2\")\n\n    instrs = \"You must set it. It must hold Mumbai MATIC.\"\n    assert alice_private_key, f\"Need envvar REMOTE_TEST_PRIVATE_KEY1. {instrs}\"\n    assert bob_private_key, f\"Need envvar REMOTE_TEST_PRIVATE_KEY2. {instrs}\"\n\n    # wallets\n    alice_wallet = Account.from_key(private_key=alice_private_key)\n    bob_wallet = Account.from_key(private_key=bob_private_key)\n\n    print(f\"alice_wallet.address = '{alice_wallet.address}'\")\n    print(f\"bob_wallet.address = '{bob_wallet.address}'\")\n\n    return (alice_wallet, bob_wallet)\n\n\n@enforce_types\ndef do_nonocean_tx_and_handle_gotchas(ocean, alice_wallet, bob_wallet):\n    \"\"\"Call wallet.transfer(), but handle several gotchas for this test use case:\n    - if the test has to repeat, there are nonce errors. Avoid via unique\n    - if there are insufficient funds, since they're hard to replace\n      automatically in remote testnets, then just skip\n    \"\"\"\n    # Simplest possible tx: Alice send Bob some fake MATIC\n    web3 = ocean.config_dict[\"web3_instance\"]\n    bob_eth_before = web3.eth.get_balance(bob_wallet.address)\n    normalized_unixtime = time.time() / 1e9\n    amt_send = 1e-8 * (random.random() + normalized_unixtime)\n\n    print(\"Do a send-Ether tx...\")\n    try:\n        priority_fee, _ = get_gas_fees()\n        send_ether(\n            ocean.config_dict,\n            alice_wallet,\n            bob_wallet.address,\n            to_wei(amt_send),\n            priority_fee=priority_fee,\n        )\n        bob_eth_after = web3.eth.get_balance(bob_wallet.address)\n    except Exception as e:\n        if error_is_skippable(str(e)):\n            warnings.warn(UserWarning(f\"Warning: EVM reported error: {e}\"))\n            return\n        raise (e)\n\n    assert bob_eth_after > bob_eth_before\n    print(\"Success\")\n\n\n@enforce_types\ndef do_ocean_tx_and_handle_gotchas(ocean, alice_wallet):\n    \"\"\"Call create() from data NFT, but handle several gotchas for this test use case:\n    - if the test has to repeat, there are nonce errors. Avoid via unique\n    - if there are insufficient funds, since they're hard to replace\n      automatically in remote testnets, then just skip\n    \"\"\"\n    # Alice publish data NFT\n    # avoid \"replacement transaction underpriced\" error: make each tx diff't\n    symbol = random_chars()\n\n    print(\"Call create() from data NFT, and wait for it to complete...\")\n    num_retries = 2\n    while num_retries != 0:\n        try:\n            priority_fee, max_fee = get_gas_fees()\n            data_nft = ocean.data_nft_factory.create(\n                {\n                    \"from\": alice_wallet,\n                    \"maxPriorityFeePerGas\": priority_fee,\n                    \"maxFeePerGas\": max_fee,\n                    \"gas\": ocean.config_dict[\"web3_instance\"]\n                    .eth.get_block(\"latest\")\n                    .gasLimit,\n                },\n                symbol,\n                symbol,\n            )\n            data_nft_symbol = data_nft.symbol()\n            break\n        except Exception as e:\n            if error_is_skippable(str(e)):\n                warnings.warn(UserWarning(f\"Warning: EVM reported error: {e}\"))\n                return\n            if \"Tx dropped\" in str(e):\n                num_retries -= 1\n                warnings.warn(\n                    UserWarning(f\"Warning: EVM reported error: {e}\\n Retrying...\")\n                )\n                continue\n            raise (e)\n\n    assert data_nft_symbol == symbol\n    print(\"Success\")\n\n\n@enforce_types\ndef error_is_skippable(error_s: str) -> bool:\n    return (\n        \"insufficient funds\" in error_s\n        or \"underpriced\" in error_s\n        or \"exceeds block gas limit\" in error_s\n        or \"exceeds the configured cap\" in error_s\n        or \"No contract deployed at\" in error_s\n        or \"nonce too low\" in error_s\n        or \"Internal error\" in error_s\n        or \"execution reverted\" in error_s\n        or \"No data was returned - the call likely reverted\" in error_s\n        or \"The field extraData is 97 bytes, but should be 32.\" in error_s\n    )\n\n\n@enforce_types\ndef random_chars() -> str:\n    cand_chars = string.ascii_uppercase + string.digits\n    s = \"\".join(random.choices(cand_chars, k=8)) + str(time.time())\n    return s\n"
  },
  {
    "path": "tests/readmes/conftest.py",
    "content": "from conftest_ganache import *\n"
  },
  {
    "path": "tests/readmes/test_readmes.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport pathlib\nimport runpy\n\nimport pytest\n\n# This file tests READMEs on local chain (ganache).\n# For tests of READMEs on remote chains, see tests/integration/remote/\n\nscripts = pathlib.Path(__file__, \"..\", \"..\", \"generated-readmes\").resolve().glob(\"*.py\")\nscript_names = [script.name for script in scripts if script.name != \"__init__.py\"]\n\n\nclass TestReadmes(object):\n    @classmethod\n    def setup_class(self):\n        globs = {}\n        prerequisite = pathlib.Path(\n            __file__,\n            \"..\",\n            \"..\",\n            \"generated-readmes/test_setup-local.py\",\n        )\n        result = runpy.run_path(str(prerequisite), run_name=\"__main__\")\n        for key in [\"os\", \"config\", \"ocean\", \"alice\", \"bob\", \"carlos\"]:\n            globs[key] = result[key]\n\n        self.globs = globs\n\n    @pytest.mark.parametrize(\"script_name\", script_names)\n    def test_script_execution(self, script_name):\n        # README generation command:\n        # mkcodes --github --output tests/generated-readmes/test_{name}.{ext} READMEs\n\n        skippable = [\n            \"c2d-flow-more-examples\",\n            \"developers\",\n            \"df\",\n            \"install\",\n            \"parameters\",\n            \"predict-eth\",\n            \"services\",\n            \"setup-local\",\n            \"setup-remote\",\n            \"publish-flow-credentials\",\n            \"publish-flow-restapi\",  # TODO: fix and restore\n            \"gas-strategy-remote\",\n            \"using-clef\",  # no way to approve transactions through automatic readme\n        ]\n\n        if script_name.replace(\"test_\", \"\").replace(\".py\", \"\") in skippable:\n            return\n\n        script = pathlib.Path(__file__, \"..\", \"..\", \"generated-readmes\", script_name)\n        runpy.run_path(str(script), run_name=\"__main__\", init_globals=self.globs)\n"
  },
  {
    "path": "tests/resources/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "tests/resources/ddo/ddo_algorithm.json",
    "content": "{\n  \"@context\": \"https://w3id.org/did/v1\",\n  \"created\": \"2019-02-08T08:13:49Z\",\n  \"id\": \"did:op:8d1b4d73e7af4634958f071ab8dfe7ab0df14019\",\n  \"proof\": {\n    \"created\": \"2019-02-08T08:13:41Z\",\n    \"creator\": \"0x37BB53e3d293494DE59fBe1FF78500423dcFd43B\",\n    \"signatureValue\": \"did:op:0bc278fee025464f8012b811d1bce8e22094d0984e4e49139df5d5ff7a028bdf\",\n    \"type\": \"DDOIntegritySignature\",\n    \"checksum\": {\n      \"0\": \"0x52b5c93b82dd9e7ecc3d9fdf4755f7f69a54484941897dc517b4adfe3bbc3377\",\n      \"1\": \"0x999999952b5c93b82dd9e7ecc3d9fdf4755f7f69a54484941897dc517b4adfe3\"\n    }\n  },\n  \"verifiableCredential\": [\n    {\n      \"@context\": [\n        \"https://www.w3.org/2018/credentials/v1\",\n        \"https://www.w3.org/2018/credentials/examples/v1\"\n      ],\n      \"id\": \"1872\",\n      \"type\": [\n        \"read\",\n        \"update\",\n        \"deactivate\"\n      ],\n      \"issuer\": \"0x610D9314EDF2ced7681BA1633C33fdb8cF365a12\",\n      \"issuanceDate\": \"2019-01-01T19:73:24Z\",\n      \"credentialSubject\": {\n        \"id\": \"0x89328493849328493284932\"\n      },\n      \"proof\": {\n        \"type\": \"RsaSignature2018\",\n        \"created\": \"2019-01-01T19:73:24Z\",\n        \"proofPurpose\": \"assertionMethod\",\n        \"signatureValue\": \"ABCJSDAO23...1tzjn4w==\"\n      }\n    }\n  ],\n  \"service\": [\n    {\n      \"index\": 0,\n      \"serviceEndpoint\": \"http://localhost:5000/api/v1/aquarius/assets/ddo/{did}\",\n      \"type\": \"metadata\",\n      \"attributes\": {\n        \"main\": {\n          \"author\": \"John Doe\",\n          \"dateCreated\": \"2019-02-08T08:13:49Z\",\n          \"license\": \"CC-BY\",\n          \"name\": \"My super algorithm\",\n          \"type\": \"algorithm\",\n          \"algorithm\": {\n            \"language\": \"scala\",\n            \"format\": \"docker-image\",\n            \"version\": \"0.1\",\n            \"container\": {\n              \"entrypoint\": \"node $ALGO\",\n              \"image\": \"node\",\n              \"tag\": \"10\"\n            }\n          },\n          \"files\": [\n            {\n              \"name\": \"build_model\",\n              \"url\": \"https://raw.githubusercontent.com/oceanprotocol/test-algorithm/master/javascript/algo.js\",\n              \"index\": 0,\n              \"checksum\": \"efb2c764274b745f5fc37f97c6b0e761\",\n              \"contentLength\": \"4535431\",\n              \"contentType\": \"text/plain\",\n              \"encoding\": \"UTF-8\",\n              \"compression\": \"zip\"\n            }\n          ]\n        },\n        \"additionalInformation\": {\n          \"description\": \"Workflow to aggregate weather information\",\n          \"tags\": [\n            \"weather\",\n            \"uk\",\n            \"2011\",\n            \"workflow\",\n            \"aggregation\"\n          ],\n          \"copyrightHolder\": \"John Doe\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "tests/resources/ddo/ddo_algorithm2.json",
    "content": "{\n  \"@context\": [\"https://w3id.org/did/v1\"],\n  \"id\": \"did:op:ACce67694eD2848dd683c651Dab7Af823b7dd123\",\n  \"version\": \"4.1.0\",\n  \"chainId\": 8996,\n  \"nftAddress\": \"0xabc\",\n  \"metadata\": {\n    \"created\": \"2020-11-15T12:27:48Z\",\n    \"updated\": \"2021-05-17T21:58:02Z\",\n    \"description\": \"Sample description\",\n    \"name\": \"Sample asset\",\n    \"type\": \"algorithm\",\n    \"author\": \"OPF\",\n    \"license\": \"https://market.oceanprotocol.com/terms\",\n    \"algorithm\": {\n        \"container\": {\n            \"entrypoint\": \"python $ALGO\",\n            \"image\": \"oceanprotocol/algo_dockers\",\n            \"tag\": \"python-branin\",\n            \"checksum\": \"sha256:8221d20c1c16491d7d56b9657ea09082c0ee4a8ab1a6621fa720da58b09580e4\"\n        }\n    }\n  },\n  \"services\": [\n    {\n      \"id\": \"1\",\n      \"type\": \"compute\",\n      \"files\": \"0x0000\",\n      \"name\": \"Download service\",\n      \"description\": \"Download service\",\n      \"datatokenAddress\": \"0x123\",\n      \"serviceEndpoint\": \"http://172.15.0.4:8030\",\n      \"timeout\": 0\n    }\n  ],\n  \"credentials\": {\n    \"allow\": [ ],\n    \"deny\": [ ]\n  },\n  \"nft\": {\n    \"address\": \"0x000000\",\n    \"name\": \"Ocean Protocol Asset v4\",\n    \"symbol\": \"OCEAN-A-v4\",\n    \"owner\": \"0x0000000\",\n    \"state\": 0,\n    \"created\": \"2000-10-31T01:30:00\"\n  },\n\n  \"datatokens\": [\n    {\n      \"address\": \"0x000000\",\n      \"name\": \"Datatoken 1\",\n      \"symbol\": \"DT-1\",\n      \"serviceId\": \"1\"\n    }\n  ],\n\n  \"event\": {\n    \"tx\": \"0x8d127de58509be5dfac600792ad24cc9164921571d168bff2f123c7f1cb4b11c\",\n    \"block\": 12831214,\n    \"from\": \"0xAcca11dbeD4F863Bb3bC2336D3CE5BAC52aa1f83\",\n    \"contract\": \"0x1a4b70d8c9DcA47cD6D0Fb3c52BB8634CA1C0Fdf\",\n    \"datetime\": \"2000-10-31T01:30:00\"\n  },\n\n  \"stats\": {\n    \"consumes\": 4\n  }\n}\n"
  },
  {
    "path": "tests/resources/ddo/ddo_sa_sample.json",
    "content": "{\n  \"@context\": \"https://w3id.org/did/v1\",\n  \"created\": \"2019-02-08T08:13:49Z\",\n  \"updated\": \"2019-03-08T08:13:49Z\",\n  \"id\": \"did:op:8d1b4d73e7af4634958f071ab8dfe7ab0df14019\",\n  \"proof\": {\n    \"created\": \"2019-02-08T08:13:41Z\",\n    \"creator\": \"0x37BB53e3d293494DE59fBe1FF78500423dcFd43B\",\n    \"signatureValue\": \"did:op:0bc278fee025464f8012b811d1bce8e22094d0984e4e49139df5d5ff7a028bdf\",\n    \"type\": \"DDOIntegritySignature\",\n    \"checksum\": {\n      \"0\": \"0x52b5c93b82dd9e7ecc3d9fdf4755f7f69a54484941897dc517b4adfe3bbc3377\",\n      \"1\": \"0x999999952b5c93b82dd9e7ecc3d9fdf4755f7f69a54484941897dc517b4adfe3\"\n    }\n  },\n  \"verifiableCredential\": [\n    {\n      \"@context\": [\n        \"https://www.w3.org/2018/credentials/v1\",\n        \"https://www.w3.org/2018/credentials/examples/v1\"\n      ],\n      \"id\": \"1872\",\n      \"type\": [\n        \"read\",\n        \"update\",\n        \"deactivate\"\n      ],\n      \"issuer\": \"0x610D9314EDF2ced7681BA1633C33fdb8cF365a12\",\n      \"issuanceDate\": \"2019-01-01T19:73:24Z\",\n      \"credentialSubject\": {\n        \"id\": \"0x89328493849328493284932\"\n      },\n      \"proof\": {\n        \"type\": \"RsaSignature2018\",\n        \"created\": \"2019-01-01T19:73:24Z\",\n        \"proofPurpose\": \"assertionMethod\",\n        \"signatureValue\": \"ABCJSDAO23...1tzjn4w==\"\n      }\n    }\n  ],\n  \"service\": [\n    {\n      \"index\": 0,\n      \"serviceEndpoint\": \"http://localhost:5000/api/v1/aquarius/assets/ddo/{did}\",\n      \"type\": \"metadata\",\n      \"attributes\": {\n        \"encryptedFiles\": \"0x2e48ceefcca7abb024f90c87c676fce8f7913f889605a349c08c0c4a822c69ad651e122cc81db4fbb52938ac627786491514f37a2ebfd04fd98ec726f1d9061ed52f13fde132222af34d9af8ec358429cf45fc669f81a607185cb9a8150df3cbb2b4e3e382fb16429be228ddd920f061b78dd54701025fac8aab976239fb31a5b60a57393e96a338324c5ac8a5600a1247339c4835533cecdb5b53caf6b6f9d6478b579b7426f650a4154a20d18a9d49f509770af62647a57fc174741b47af3c8beeaaa76bee276cce8fba1f3fec0e1c\",\n        \"main\": {\n          \"author\": \"Met Office\",\n          \"dateCreated\": \"2019-02-08T08:13:49Z\",\n          \"files\": [\n            {\n              \"url\": \"https://raw.githubusercontent.com/tbertinmahieux/MSongsDB/master/Tasks_Demos/CoverSongs/shs_dataset_test.txt\",\n              \"index\": 0,\n              \"checksum\": \"efb2c764274b745f5fc37f97c6b0e761\",\n              \"contentLength\": \"4535431\",\n              \"contentType\": \"text/csv\",\n              \"encoding\": \"UTF-8\",\n              \"compression\": \"zip\"\n            }\n          ],\n          \"license\": \"CC-BY\",\n          \"name\": \"UK Weather information 2011\",\n          \"type\": \"dataset\"\n        },\n        \"additionalInformation\": {\n          \"description\": \"Weather information of UK including temperature and humidity\",\n          \"tags\": [\n            \"weather\",\n            \"uk\",\n            \"2011\",\n            \"temperature\",\n            \"humidity\"\n          ],\n          \"workExample\": \"423432fsd,51.509865,-0.118092,2011-01-01T10:55:11+00:00,7.2,68\",\n          \"copyrightHolder\": \"Met Office\",\n          \"links\": [\n            {\n              \"name\": \"Sample of Asset Data\",\n              \"type\": \"sample\",\n              \"url\": \"https://foo.com/sample.csv\"\n            },\n            {\n              \"name\": \"Data Format Definition\",\n              \"type\": \"format\",\n              \"url\": \"https://foo.com/sampl2.csv\"\n            }\n          ],\n          \"inLanguage\": \"en\",\n          \"updateFrecuency\": \"yearly\",\n          \"structuredMarkup\": [\n            {\n              \"uri\": \"http://skos.um.es/unescothes/C01194/jsonld\",\n              \"mediaType\": \"application/ld+json\"\n            },\n            {\n              \"uri\": \"http://skos.um.es/unescothes/C01194/turtle\",\n              \"mediaType\": \"text/turtle\"\n            }\n          ]\n        },\n        \"curation\": {\n          \"numVotes\": 123,\n          \"rating\": 0.0,\n          \"schema\": \"Binary Votting\",\n          \"isListed\": true\n        }\n      }\n    },\n    {\n      \"type\": \"access\",\n      \"index\": 1,\n      \"serviceEndpoint\": \"http://localhost:8030\",\n      \"attributes\": {\n        \"main\": {\n          \"name\": \"dataAssetAccessServiceAgreement\",\n          \"creator\": \"\",\n          \"datePublished\": \"2019-02-08T08:13:49Z\",\n          \"cost\": \"1.0\",\n          \"timeout\": 36000\n        },\n        \"additionalInformation\": {\n          \"description\": \"\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "tests/resources/ddo/ddo_sa_sample_disabled.json",
    "content": "{\n  \"@context\": \"https://w3id.org/did/v1\",\n  \"created\": \"2019-02-08T08:13:49Z\",\n  \"updated\": \"2019-03-08T08:13:49Z\",\n  \"id\": \"did:op:8d1b4d73e7af4634958f071ab8dfe7ab0df14019\",\n  \"proof\": {\n    \"created\": \"2019-02-08T08:13:41Z\",\n    \"creator\": \"0x37BB53e3d293494DE59fBe1FF78500423dcFd43B\",\n    \"signatureValue\": \"did:op:0bc278fee025464f8012b811d1bce8e22094d0984e4e49139df5d5ff7a028bdf\",\n    \"type\": \"DDOIntegritySignature\",\n    \"checksum\": {\n      \"0\": \"0x52b5c93b82dd9e7ecc3d9fdf4755f7f69a54484941897dc517b4adfe3bbc3377\",\n      \"1\": \"0x999999952b5c93b82dd9e7ecc3d9fdf4755f7f69a54484941897dc517b4adfe3\"\n    }\n  },\n  \"verifiableCredential\": [\n    {\n      \"@context\": [\n        \"https://www.w3.org/2018/credentials/v1\",\n        \"https://www.w3.org/2018/credentials/examples/v1\"\n      ],\n      \"id\": \"1872\",\n      \"type\": [\n        \"read\",\n        \"update\",\n        \"deactivate\"\n      ],\n      \"issuer\": \"0x610D9314EDF2ced7681BA1633C33fdb8cF365a12\",\n      \"issuanceDate\": \"2019-01-01T19:73:24Z\",\n      \"credentialSubject\": {\n        \"id\": \"0x89328493849328493284932\"\n      },\n      \"proof\": {\n        \"type\": \"RsaSignature2018\",\n        \"created\": \"2019-01-01T19:73:24Z\",\n        \"proofPurpose\": \"assertionMethod\",\n        \"signatureValue\": \"ABCJSDAO23...1tzjn4w==\"\n      }\n    }\n  ],\n  \"service\": [\n    {\n      \"index\": 0,\n      \"serviceEndpoint\": \"http://localhost:5000/api/v1/aquarius/assets/ddo/{did}\",\n      \"type\": \"metadata\",\n      \"attributes\": {\n        \"encryptedFiles\": \"0x2e48ceefcca7abb024f90c87c676fce8f7913f889605a349c08c0c4a822c69ad651e122cc81db4fbb52938ac627786491514f37a2ebfd04fd98ec726f1d9061ed52f13fde132222af34d9af8ec358429cf45fc669f81a607185cb9a8150df3cbb2b4e3e382fb16429be228ddd920f061b78dd54701025fac8aab976239fb31a5b60a57393e96a338324c5ac8a5600a1247339c4835533cecdb5b53caf6b6f9d6478b579b7426f650a4154a20d18a9d49f509770af62647a57fc174741b47af3c8beeaaa76bee276cce8fba1f3fec0e1c\",\n        \"status\": {\n            \"isOrderDisabled\": true\n         },\n        \"main\": {\n          \"author\": \"Met Office\",\n          \"dateCreated\": \"2019-02-08T08:13:49Z\",\n          \"files\": [\n            {\n              \"url\": \"https://raw.githubusercontent.com/tbertinmahieux/MSongsDB/master/Tasks_Demos/CoverSongs/shs_dataset_test.txt\",\n              \"index\": 0,\n              \"checksum\": \"efb2c764274b745f5fc37f97c6b0e761\",\n              \"contentLength\": \"4535431\",\n              \"contentType\": \"text/csv\",\n              \"encoding\": \"UTF-8\",\n              \"compression\": \"zip\"\n            }\n          ],\n          \"license\": \"CC-BY\",\n          \"name\": \"UK Weather information 2011\",\n          \"type\": \"dataset\"\n        },\n        \"additionalInformation\": {\n          \"description\": \"Weather information of UK including temperature and humidity\",\n          \"tags\": [\n            \"weather\",\n            \"uk\",\n            \"2011\",\n            \"temperature\",\n            \"humidity\"\n          ],\n          \"workExample\": \"423432fsd,51.509865,-0.118092,2011-01-01T10:55:11+00:00,7.2,68\",\n          \"copyrightHolder\": \"Met Office\",\n          \"links\": [\n            {\n              \"name\": \"Sample of Asset Data\",\n              \"type\": \"sample\",\n              \"url\": \"https://foo.com/sample.csv\"\n            },\n            {\n              \"name\": \"Data Format Definition\",\n              \"type\": \"format\",\n              \"url\": \"https://foo.com/sampl2.csv\"\n            }\n          ],\n          \"inLanguage\": \"en\",\n          \"updateFrecuency\": \"yearly\",\n          \"structuredMarkup\": [\n            {\n              \"uri\": \"http://skos.um.es/unescothes/C01194/jsonld\",\n              \"mediaType\": \"application/ld+json\"\n            },\n            {\n              \"uri\": \"http://skos.um.es/unescothes/C01194/turtle\",\n              \"mediaType\": \"text/turtle\"\n            }\n          ]\n        },\n        \"curation\": {\n          \"numVotes\": 123,\n          \"rating\": 0.0,\n          \"schema\": \"Binary Votting\",\n          \"isListed\": true\n        }\n      }\n    },\n    {\n      \"type\": \"access\",\n      \"index\": 1,\n      \"serviceEndpoint\": \"http://localhost:8030\",\n      \"attributes\": {\n        \"main\": {\n          \"name\": \"dataAssetAccessServiceAgreement\",\n          \"creator\": \"\",\n          \"datePublished\": \"2019-02-08T08:13:49Z\",\n          \"cost\": \"1.0\",\n          \"timeout\": 36000\n        },\n        \"additionalInformation\": {\n          \"description\": \"\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "tests/resources/ddo/ddo_sa_sample_with_credentials.json",
    "content": "{\n  \"@context\": \"https://w3id.org/did/v1\",\n  \"created\": \"2019-02-08T08:13:49Z\",\n  \"updated\": \"2019-03-08T08:13:49Z\",\n  \"id\": \"did:op:8d1b4d73e7af4634958f071ab8dfe7ab0df14019\",\n  \"proof\": {\n    \"created\": \"2019-02-08T08:13:41Z\",\n    \"creator\": \"0x37BB53e3d293494DE59fBe1FF78500423dcFd43B\",\n    \"signatureValue\": \"did:op:0bc278fee025464f8012b811d1bce8e22094d0984e4e49139df5d5ff7a028bdf\",\n    \"type\": \"DDOIntegritySignature\",\n    \"checksum\": {\n      \"0\": \"0x52b5c93b82dd9e7ecc3d9fdf4755f7f69a54484941897dc517b4adfe3bbc3377\",\n      \"1\": \"0x999999952b5c93b82dd9e7ecc3d9fdf4755f7f69a54484941897dc517b4adfe3\"\n    }\n  },\n  \"verifiableCredential\": [\n    {\n      \"@context\": [\n        \"https://www.w3.org/2018/credentials/v1\",\n        \"https://www.w3.org/2018/credentials/examples/v1\"\n      ],\n      \"id\": \"1872\",\n      \"type\": [\n        \"read\",\n        \"update\",\n        \"deactivate\"\n      ],\n      \"issuer\": \"0x610D9314EDF2ced7681BA1633C33fdb8cF365a12\",\n      \"issuanceDate\": \"2019-01-01T19:73:24Z\",\n      \"credentialSubject\": {\n        \"id\": \"0x89328493849328493284932\"\n      },\n      \"proof\": {\n        \"type\": \"RsaSignature2018\",\n        \"created\": \"2019-01-01T19:73:24Z\",\n        \"proofPurpose\": \"assertionMethod\",\n        \"signatureValue\": \"ABCJSDAO23...1tzjn4w==\"\n      }\n    }\n  ],\n  \"service\": [\n    {\n      \"index\": 0,\n      \"serviceEndpoint\": \"http://localhost:5000/api/v1/aquarius/assets/ddo/{did}\",\n      \"type\": \"metadata\",\n      \"attributes\": {\n        \"encryptedFiles\": \"0x2e48ceefcca7abb024f90c87c676fce8f7913f889605a349c08c0c4a822c69ad651e122cc81db4fbb52938ac627786491514f37a2ebfd04fd98ec726f1d9061ed52f13fde132222af34d9af8ec358429cf45fc669f81a607185cb9a8150df3cbb2b4e3e382fb16429be228ddd920f061b78dd54701025fac8aab976239fb31a5b60a57393e96a338324c5ac8a5600a1247339c4835533cecdb5b53caf6b6f9d6478b579b7426f650a4154a20d18a9d49f509770af62647a57fc174741b47af3c8beeaaa76bee276cce8fba1f3fec0e1c\",\n        \"main\": {\n          \"author\": \"Met Office\",\n          \"dateCreated\": \"2019-02-08T08:13:49Z\",\n          \"files\": [\n            {\n              \"url\": \"https://raw.githubusercontent.com/tbertinmahieux/MSongsDB/master/Tasks_Demos/CoverSongs/shs_dataset_test.txt\",\n              \"index\": 0,\n              \"checksum\": \"efb2c764274b745f5fc37f97c6b0e761\",\n              \"contentLength\": \"4535431\",\n              \"contentType\": \"text/csv\",\n              \"encoding\": \"UTF-8\",\n              \"compression\": \"zip\"\n            }\n          ],\n          \"license\": \"CC-BY\",\n          \"name\": \"UK Weather information 2011\",\n          \"type\": \"dataset\"\n        },\n        \"additionalInformation\": {\n          \"description\": \"Weather information of UK including temperature and humidity\",\n          \"tags\": [\n            \"weather\",\n            \"uk\",\n            \"2011\",\n            \"temperature\",\n            \"humidity\"\n          ],\n          \"workExample\": \"423432fsd,51.509865,-0.118092,2011-01-01T10:55:11+00:00,7.2,68\",\n          \"copyrightHolder\": \"Met Office\",\n          \"links\": [\n            {\n              \"name\": \"Sample of Asset Data\",\n              \"type\": \"sample\",\n              \"url\": \"https://foo.com/sample.csv\"\n            },\n            {\n              \"name\": \"Data Format Definition\",\n              \"type\": \"format\",\n              \"url\": \"https://foo.com/sampl2.csv\"\n            }\n          ],\n          \"inLanguage\": \"en\",\n          \"updateFrecuency\": \"yearly\",\n          \"structuredMarkup\": [\n            {\n              \"uri\": \"http://skos.um.es/unescothes/C01194/jsonld\",\n              \"mediaType\": \"application/ld+json\"\n            },\n            {\n              \"uri\": \"http://skos.um.es/unescothes/C01194/turtle\",\n              \"mediaType\": \"text/turtle\"\n            }\n          ]\n        },\n        \"curation\": {\n          \"numVotes\": 123,\n          \"rating\": 0.0,\n          \"schema\": \"Binary Votting\",\n          \"isListed\": true\n        }\n      }\n    },\n    {\n      \"type\": \"access\",\n      \"index\": 1,\n      \"serviceEndpoint\": \"http://localhost:8030\",\n      \"attributes\": {\n        \"main\": {\n          \"name\": \"dataAssetAccessServiceAgreement\",\n          \"creator\": \"\",\n          \"datePublished\": \"2019-02-08T08:13:49Z\",\n          \"cost\": \"1.0\",\n          \"timeout\": 36000\n        },\n        \"additionalInformation\": {\n          \"description\": \"\"\n        }\n      }\n    }\n  ],\n  \"credentials\": {\n    \"allow\": [\n        {\"type\": \"address\", \"values\": [\"0x123\", \"0x456A\"]}\n    ],\n    \"deny\": [\n        {\"type\": \"address\", \"values\": [\"0x2222\", \"0x333\"]}\n    ]\n  }\n}\n"
  },
  {
    "path": "tests/resources/ddo/ddo_sample_algorithm.json",
    "content": "{\n  \"@context\": \"https://w3id.org/did/v1\",\n  \"created\": \"2019-02-08T08:13:49Z\",\n  \"id\": \"did:op:8d1b4d73e7af4634958f071ab8dfe7ab0df14019\",\n  \"proof\": {\n    \"created\": \"2019-02-08T08:13:41Z\",\n    \"creator\": \"0x37BB53e3d293494DE59fBe1FF78500423dcFd43B\",\n    \"signatureValue\": \"did:op:0bc278fee025464f8012b811d1bce8e22094d0984e4e49139df5d5ff7a028bdf\",\n    \"type\": \"DDOIntegritySignature\",\n    \"checksum\": {\n      \"0\": \"0x52b5c93b82dd9e7ecc3d9fdf4755f7f69a54484941897dc517b4adfe3bbc3377\",\n      \"1\": \"0x999999952b5c93b82dd9e7ecc3d9fdf4755f7f69a54484941897dc517b4adfe3\"\n    }\n  },\n  \"verifiableCredential\": [\n    {\n      \"@context\": [\n        \"https://www.w3.org/2018/credentials/v1\",\n        \"https://www.w3.org/2018/credentials/examples/v1\"\n      ],\n      \"id\": \"1872\",\n      \"type\": [\n        \"read\",\n        \"update\",\n        \"deactivate\"\n      ],\n      \"issuer\": \"0x610D9314EDF2ced7681BA1633C33fdb8cF365a12\",\n      \"issuanceDate\": \"2019-01-01T19:73:24Z\",\n      \"credentialSubject\": {\n        \"id\": \"0x89328493849328493284932\"\n      },\n      \"proof\": {\n        \"type\": \"RsaSignature2018\",\n        \"created\": \"2019-01-01T19:73:24Z\",\n        \"proofPurpose\": \"assertionMethod\",\n        \"signatureValue\": \"ABCJSDAO23...1tzjn4w==\"\n      }\n    }\n  ],\n  \"service\": [\n    {\n      \"index\": 0,\n      \"serviceEndpoint\": \"http://localhost:5000/api/v1/aquarius/assets/ddo/{did}\",\n      \"type\": \"metadata\",\n      \"attributes\": {\n        \"main\": {\n          \"author\": \"John Doe\",\n          \"dateCreated\": \"2019-02-08T08:13:49Z\",\n          \"license\": \"CC-BY\",\n          \"name\": \"My super algorithm\",\n          \"type\": \"algorithm\",\n          \"algorithm\": {\n            \"language\": \"scala\",\n            \"format\": \"docker-image\",\n            \"version\": \"0.1\",\n            \"container\": {\n              \"entrypoint\": \"node $ALGO\",\n              \"image\": \"node\",\n              \"tag\": \"10\"\n            }\n          },\n          \"files\": [\n            {\n              \"name\": \"build_model\",\n              \"url\": \"https://raw.githubusercontent.com/oceanprotocol/test-algorithm/master/javascript/algo.js\",\n              \"index\": 0,\n              \"checksum\": \"efb2c764274b745f5fc37f97c6b0e761\",\n              \"contentLength\": \"4535431\",\n              \"contentType\": \"text/plain\",\n              \"encoding\": \"UTF-8\",\n              \"compression\": \"zip\"\n            }\n          ]\n        },\n        \"additionalInformation\": {\n          \"description\": \"Workflow to aggregate weather information\",\n          \"tags\": [\n            \"weather\",\n            \"uk\",\n            \"2011\",\n            \"workflow\",\n            \"aggregation\"\n          ],\n          \"copyrightHolder\": \"John Doe\"\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "tests/resources/ddo/ddo_v4_sample.json",
    "content": "{\n  \"@context\": [\"https://w3id.org/did/v1\"],\n  \"id\": \"did:op:d32696f71f3318c92bcf325e2e51e6e8299c0eb6d362ddcfa77d2a3e0c1237b5\",\n  \"version\": \"4.1.0\",\n  \"chainId\": 8996,\n  \"nftAddress\": \"0xCc708430E6a174BD4639A979F578A2176A0FA3fA\",\n  \"metadata\": {\n    \"created\": \"2020-11-15T12:27:48Z\",\n    \"updated\": \"2021-05-17T21:58:02Z\",\n    \"description\": \"Sample description\",\n    \"name\": \"Sample asset\",\n    \"type\": \"dataset\",\n    \"author\": \"OPF\",\n    \"license\": \"https://market.oceanprotocol.com/terms\"\n  },\n  \"services\": [\n    {\n      \"id\": \"1\",\n      \"type\": \"access\",\n      \"files\": \"0x0000\",\n      \"name\": \"Download service\",\n      \"description\": \"Download service\",\n      \"datatokenAddress\": \"0x123\",\n      \"serviceEndpoint\": \"http://172.15.0.4:8030\",\n      \"timeout\": 0\n    }\n  ],\n  \"credentials\": {\n    \"allow\": [\n      {\n        \"type\": \"address\",\n        \"values\": [\"0x123\", \"0x456\"]\n      }\n    ],\n    \"deny\": [\n      {\n        \"type\": \"address\",\n        \"values\": [\"0x2222\", \"0x333\"]\n      }\n    ]\n  },\n  \"nft\": {\n    \"address\": \"0xCc708430E6a174BD4639A979F578A2176A0FA3fA\",\n    \"name\": \"Ocean Protocol Asset v4\",\n    \"symbol\": \"OCEAN-A-v4\",\n    \"owner\": \"0x0000000\",\n    \"state\": 0,\n    \"created\": \"2000-10-31T01:30:00\"\n  },\n\n  \"datatokens\": [\n    {\n      \"address\": \"0x000000\",\n      \"name\": \"Datatoken 1\",\n      \"symbol\": \"DT-1\",\n      \"serviceId\": \"1\"\n    }\n  ],\n\n  \"event\": {\n    \"tx\": \"0x8d127de58509be5dfac600792ad24cc9164921571d168bff2f123c7f1cb4b11c\",\n    \"block\": 12831214,\n    \"from\": \"0xAcca11dbeD4F863Bb3bC2336D3CE5BAC52aa1f83\",\n    \"contract\": \"0x1a4b70d8c9DcA47cD6D0Fb3c52BB8634CA1C0Fdf\",\n    \"datetime\": \"2000-10-31T01:30:00\"\n  },\n\n  \"stats\": {\n    \"consumes\": 4\n  }\n}\n"
  },
  {
    "path": "tests/resources/ddo/ddo_v4_with_compute_service.json",
    "content": "{\n  \"@context\": [\"https://w3id.org/did/v1\"],\n  \"id\": \"did:op:d32696f71f3318c92bcf325e2e51e6e8299c0eb6d362ddcfa77d2a3e0c1237b5\",\n  \"version\": \"4.1.0\",\n  \"chainId\": 8996,\n  \"nftAddress\": \"0xCc708430E6a174BD4639A979F578A2176A0FA3fA\",\n  \"metadata\": {\n    \"created\": \"2020-11-15T12:27:48Z\",\n    \"updated\": \"2021-05-17T21:58:02Z\",\n    \"description\": \"Sample description\",\n    \"name\": \"Sample asset\",\n    \"type\": \"dataset\",\n    \"author\": \"OPF\",\n    \"license\": \"https://market.oceanprotocol.com/terms\"\n  },\n  \"services\": [\n    {\n      \"id\": \"1\",\n      \"type\": \"access\",\n      \"files\": \"0x0000\",\n      \"name\": \"Download service\",\n      \"description\": \"Download service\",\n      \"datatokenAddress\": \"0x123\",\n      \"serviceEndpoint\": \"http://172.15.0.4:8030\",\n      \"timeout\": 0\n    },\n    {\n      \"id\": \"2\",\n      \"type\": \"compute\",\n      \"files\": \"0x0001\",\n      \"name\": \"Compute service\",\n      \"description\": \"Compute service\",\n      \"datatokenAddress\": \"0x124\",\n      \"serviceEndpoint\": \"http://172.15.0.4:8030\",\n      \"timeout\": 3600,\n      \"compute\": {\n        \"namespace\": \"ocean-compute\",\n        \"cpus\": 2,\n        \"gpus\": 4,\n        \"gpuType\": \"NVIDIA Tesla V100 GPU\",\n        \"memory\": \"128M\",\n        \"volumeSize\": \"2G\",\n        \"allowRawAlgorithm\": false,\n        \"allowNetworkAccess\": true,\n        \"publisherTrustedAlgorithmPublishers\": [\"0x234\", \"0x235\"],\n        \"publisherTrustedAlgorithms\": [\n          {\n            \"did\": \"did:op:123\",\n            \"filesChecksum\": \"100\",\n            \"containerSectionChecksum\": \"200\"\n          },\n          {\n            \"did\": \"did:op:124\",\n            \"filesChecksum\": \"110\",\n            \"containerSectionChecksum\": \"210\"\n          }\n        ]\n      }\n    }\n  ],\n  \"credentials\": {\n    \"allow\": [\n      {\n        \"type\": \"address\",\n        \"values\": [\"0x123\", \"0x456\"]\n      }\n    ],\n    \"deny\": [\n      {\n        \"type\": \"address\",\n        \"values\": [\"0x2222\", \"0x333\"]\n      }\n    ]\n  },\n  \"nft\": {\n    \"address\": \"0xCc708430E6a174BD4639A979F578A2176A0FA3fA\",\n    \"name\": \"Ocean Protocol Asset v4\",\n    \"symbol\": \"OCEAN-A-v4\",\n    \"owner\": \"0x0000000\",\n    \"state\": 0,\n    \"created\": \"2000-10-31T01:30:00\"\n  },\n\n  \"datatokens\": [\n    {\n      \"address\": \"0x000000\",\n      \"name\": \"Datatoken 1\",\n      \"symbol\": \"DT-1\",\n      \"serviceId\": \"1\"\n    },\n    {\n      \"address\": \"0x000001\",\n      \"name\": \"Datatoken 2\",\n      \"symbol\": \"DT-2\",\n      \"serviceId\": \"2\"\n    }\n  ],\n\n  \"event\": {\n    \"tx\": \"0x8d127de58509be5dfac600792ad24cc9164921571d168bff2f123c7f1cb4b11c\",\n    \"block\": 12831214,\n    \"from\": \"0xAcca11dbeD4F863Bb3bC2336D3CE5BAC52aa1f83\",\n    \"contract\": \"0x1a4b70d8c9DcA47cD6D0Fb3c52BB8634CA1C0Fdf\",\n    \"datetime\": \"2000-10-31T01:30:00\"\n  },\n\n  \"stats\": {\n    \"consumes\": 4\n  }\n}\n"
  },
  {
    "path": "tests/resources/ddo/ddo_v4_with_compute_service2.json",
    "content": "{\n  \"@context\": [\"https://w3id.org/did/v1\"],\n  \"id\": \"did:op:d32696f71f3318c92bcf325e2e51e6e8299c0eb6d362ddcfa77d2a3e0c1237b5\",\n  \"version\": \"4.1.0\",\n  \"chainId\": 8996,\n  \"nftAddress\": \"0xCc708430E6a174BD4639A979F578A2176A0FA3fA\",\n  \"metadata\": {\n    \"created\": \"2020-11-15T12:27:48Z\",\n    \"updated\": \"2021-05-17T21:58:02Z\",\n    \"description\": \"Sample description\",\n    \"name\": \"Sample asset\",\n    \"type\": \"dataset\",\n    \"author\": \"OPF\",\n    \"license\": \"https://market.oceanprotocol.com/terms\"\n  },\n  \"services\": [\n    {\n      \"id\": \"1\",\n      \"type\": \"access\",\n      \"files\": \"0x0000\",\n      \"name\": \"Download service\",\n      \"description\": \"Download service\",\n      \"datatokenAddress\": \"0x123\",\n      \"serviceEndpoint\": \"http://172.15.0.4:8030\",\n      \"timeout\": 0\n    },\n    {\n      \"id\": \"2\",\n      \"type\": \"compute\",\n      \"files\": \"0x0001\",\n      \"name\": \"Compute service\",\n      \"description\": \"Compute service\",\n      \"datatokenAddress\": \"0x124\",\n      \"serviceEndpoint\": \"http://172.15.0.4:8030\",\n      \"timeout\": 3600,\n      \"compute\": {\n        \"namespace\": \"ocean-compute\",\n        \"cpus\": 2,\n        \"gpus\": 4,\n        \"gpuType\": \"NVIDIA Tesla V100 GPU\",\n        \"memory\": \"128M\",\n        \"volumeSize\": \"2G\",\n        \"allowRawAlgorithm\": false,\n        \"allowNetworkAccess\": true,\n        \"publisherTrustedAlgorithmPublishers\": [\"0xabc\"],\n        \"publisherTrustedAlgorithms\": [\n          {\n            \"did\": \"did:op:123\",\n            \"filesChecksum\":\"5ce12db0cc7f13f963b1af3b5df7cab4fd3ffae16c8af7e6e416570d197dcc61\",\n            \"containerSectionChecksum\": \"77ac49b8c4ad209694f7143cec62d67ee4c6d2d5edf1f3f4e09ca4173b22610f\"\n          }\n        ]\n      }\n    }\n  ],\n  \"credentials\": {\n    \"allow\": [\n      {\n        \"type\": \"address\",\n        \"values\": [\"0x123\", \"0x456\"]\n      }\n    ],\n    \"deny\": [\n      {\n        \"type\": \"address\",\n        \"values\": [\"0x2222\", \"0x333\"]\n      }\n    ]\n  },\n  \"nft\": {\n    \"address\": \"0xCc708430E6a174BD4639A979F578A2176A0FA3fA\",\n    \"name\": \"Ocean Protocol Asset v4\",\n    \"symbol\": \"OCEAN-A-v4\",\n    \"owner\": \"0x0000000\",\n    \"state\": 0,\n    \"created\": \"2000-10-31T01:30:00\"\n  },\n\n  \"datatokens\": [\n    {\n      \"address\": \"0x000000\",\n      \"name\": \"Datatoken 1\",\n      \"symbol\": \"DT-1\",\n      \"serviceId\": \"1\"\n    },\n    {\n      \"address\": \"0x000001\",\n      \"name\": \"Datatoken 2\",\n      \"symbol\": \"DT-2\",\n      \"serviceId\": \"2\"\n    }\n  ],\n\n  \"event\": {\n    \"tx\": \"0x8d127de58509be5dfac600792ad24cc9164921571d168bff2f123c7f1cb4b11c\",\n    \"block\": 12831214,\n    \"from\": \"0xAcca11dbeD4F863Bb3bC2336D3CE5BAC52aa1f83\",\n    \"contract\": \"0x1a4b70d8c9DcA47cD6D0Fb3c52BB8634CA1C0Fdf\",\n    \"datetime\": \"2000-10-31T01:30:00\"\n  },\n\n  \"stats\": {\n    \"consumes\": 4\n  }\n}\n"
  },
  {
    "path": "tests/resources/ddo/ddo_with_compute_service.json",
    "content": "{\n  \"@context\": \"https://w3id.org/future-method/v1\",\n  \"created\": \"2019-04-09T19:02:11Z\",\n  \"id\": \"did:op:8d1b4d73e7af4634958f071ab8dfe7ab0df14019\",\n  \"proof\": {\n    \"created\": \"2019-04-09T19:02:11Z\",\n    \"creator\": \"0x00Bd138aBD70e2F00903268F3Db08f2D25677C9e\",\n    \"signatureValue\": \"1cd57300733bcbcda0beb59b3e076de6419c0d7674e7befb77820b53c79e3aa8f1776effc64cf088bad8cb694cc4d71ebd74a13b2f75893df5a53f3f318f6cf828\",\n    \"type\": \"DDOIntegritySignature\"\n  },\n  \"service\": [\n    {\n      \"type\": \"metadata\",\n      \"index\": 0,\n      \"serviceEndpoint\": \"http://myaquarius.org/api/v1/provider/assets/metadata/{did}\",\n      \"attributes\": {\n        \"main\": {\n          \"author\": \"Met Office\",\n          \"dateCreated\": \"2019-02-08T08:13:49Z\",\n          \"files\": [\n            {\n              \"url\": \"https://raw.githubusercontent.com/tbertinmahieux/MSongsDB/master/Tasks_Demos/CoverSongs/shs_dataset_test.txt\",\n              \"index\": 0,\n              \"checksum\": \"efb2c764274b745f5fc37f97c6b0e764\",\n              \"contentLength\": \"4535431\",\n              \"contentType\": \"text/csv\",\n              \"encoding\": \"UTF-8\",\n              \"compression\": \"zip\"\n            }\n          ],\n          \"license\": \"CC-BY\",\n          \"name\": \"UK Weather information 2011\",\n          \"type\": \"dataset\"\n        },\n        \"additionalInformation\": {}\n      }\n    },\n    {\n      \"type\": \"compute\",\n      \"index\": 2,\n      \"serviceEndpoint\": \"http://myprovider.org\",\n      \"templateId\": \"\",\n      \"attributes\": {\n        \"main\": {\n          \"name\": \"dataAssetComputingServiceAgreement\",\n          \"creator\": \"0x00Bd138aBD70e2F00903268F3Db08f2D25677C9e\",\n          \"datePublished\": \"2019-04-09T19:02:11Z\",\n          \"cost\": \"1.0\",\n          \"timeout\": 86400,\n          \"privacy\": {},\n          \"provider\": {\n            \"type\": \"Azure\",\n            \"description\": \"\",\n            \"environment\": {\n              \"cluster\": {\n                \"type\": \"Kubernetes\",\n                \"url\": \"http://10.0.0.17/xxx\"\n              },\n              \"supportedContainers\": [\n                {\n                  \"image\": \"tensorflow/tensorflow\",\n                  \"tag\": \"latest\",\n                  \"checksum\": \"sha256:cb57ecfa6ebbefd8ffc7f75c0f00e57a7fa739578a429b6f72a0df19315deadc\"\n                },\n                {\n                  \"image\": \"tensorflow/tensorflow\",\n                  \"tag\": \"latest\",\n                  \"checksum\": \"sha256:cb57ecfa6ebbefd8ffc7f75c0f00e57a7fa739578a429b6f72a0df19315deadc\"\n                }\n              ],\n              \"supportedServers\": [\n                {\n                  \"serverId\": \"1\",\n                  \"serverType\": \"xlsize\",\n                  \"price\": \"50\",\n                  \"cpu\": \"16\",\n                  \"gpu\": \"0\",\n                  \"memory\": \"128gb\",\n                  \"disk\": \"160gb\",\n                  \"maxExecutionTime\": 86400\n                },\n                {\n                  \"serverId\": \"2\",\n                  \"serverType\": \"medium\",\n                  \"price\": \"10\",\n                  \"cpu\": \"2\",\n                  \"gpu\": \"0\",\n                  \"memory\": \"8gb\",\n                  \"disk\": \"80gb\",\n                  \"maxExecutionTime\": 86400\n                }\n              ]\n            }\n          }\n        },\n        \"additionalInformation\": {}\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "tests/resources/ddo/valid_metadata.json",
    "content": "{\n  \"main\": {\n    \"name\": \"10 Monkey Species Small\",\n    \"dateCreated\": \"2012-02-01T10:55:11Z\",\n    \"author\": \"Mario\",\n    \"license\": \"CC0: Public Domain\",\n    \"files\": [\n      {\n        \"index\": 0,\n        \"contentType\": \"application/zip\",\n        \"checksum\": \"2bf9d229d110d1976cdf85e9f3256c7f\",\n        \"checksumType\": \"MD5\",\n        \"contentLength\": \"12057507\",\n        \"url\": \"https://s3.amazonaws.com/datacommons-seeding-us-east/10_Monkey_Species_Small/assets/training.zip\"\n      },\n      {\n        \"index\": 1,\n        \"contentType\": \"text/text\",\n        \"checksum\": \"354d19c0733c47ef3a6cce5b633116b0\",\n        \"checksumType\": \"MD5\",\n        \"contentLength\": \"928\",\n        \"url\": \"https://s3.amazonaws.com/datacommons-seeding-us-east/10_Monkey_Species_Small/assets/monkey_labels.txt\"\n      },\n      {\n        \"index\": 2,\n        \"contentType\": \"application/zip\",\n        \"url\": \"https://s3.amazonaws.com/datacommons-seeding-us-east/10_Monkey_Species_Small/assets/validation.zip\"\n      }\n    ],\n    \"type\": \"dataset\"\n  }\n}"
  },
  {
    "path": "tests/resources/ddo_helpers.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport json\nimport os\nimport pathlib\nfrom typing import List\n\nfrom ocean_lib.assets.ddo import DDO\nfrom ocean_lib.models.datatoken_base import DatatokenArguments\nfrom ocean_lib.ocean.ocean import Ocean\nfrom ocean_lib.services.service import Service\nfrom ocean_lib.structures.file_objects import UrlFile\nfrom tests.resources.helper_functions import get_file1, get_file2\n\n\ndef get_resource_path(dir_name, file_name):\n    base = os.path.realpath(__file__).split(os.path.sep)[1:-1]\n    if dir_name:\n        return pathlib.Path(os.path.join(os.path.sep, *base, dir_name, file_name))\n    else:\n        return pathlib.Path(os.path.join(os.path.sep, *base, file_name))\n\n\ndef get_key_from_v4_sample_ddo(key, file_name=\"ddo_v4_sample.json\"):\n    path = get_resource_path(\"ddo\", file_name)\n    with open(path, \"r\") as file_handle:\n        ddo = file_handle.read()\n    ddo_dict = json.loads(ddo)\n    return ddo_dict.pop(key, None)\n\n\ndef get_sample_ddo(file_name=\"ddo_v4_sample.json\") -> dict:\n    path = get_resource_path(\"ddo\", file_name)\n    with open(path, \"r\") as file_handle:\n        ddo = file_handle.read()\n    return json.loads(ddo)\n\n\ndef get_sample_ddo_with_compute_service(\n    filename=\"ddo_v4_with_compute_service.json\",\n) -> dict:\n    path = get_resource_path(\"ddo\", filename)\n    with open(path, \"r\") as file_handle:\n        ddo = file_handle.read()\n    return json.loads(ddo)\n\n\ndef get_sample_algorithm_ddo(filename=\"ddo_algorithm.json\") -> DDO:\n    path = get_resource_path(\"ddo\", filename)\n    assert path.exists(), f\"{path} does not exist!\"\n\n    with open(path, \"r\") as file_handle:\n        metadata = file_handle.read()\n    alg_dict = json.loads(metadata)\n\n    return DDO.from_dict(alg_dict)\n\n\ndef get_default_metadata():\n    return get_key_from_v4_sample_ddo(\"metadata\")\n\n\ndef get_default_files():\n    return [get_file1(), get_file2()]\n\n\ndef build_default_services(config, datatoken):\n    files = get_default_files()\n    services = [\n        datatoken.build_access_service(\n            service_id=\"0\",\n            service_endpoint=config.get(\"PROVIDER_URL\"),\n            files=files,\n        )\n    ]\n\n    return services\n\n\ndef build_credentials_dict() -> dict:\n    \"\"\"Build a credentials dict, used for testing.\"\"\"\n    return {\"allow\": [], \"deny\": []}\n\n\ndef get_registered_asset_with_access_service(\n    ocean_instance, publisher_wallet, metadata=None, more_files=False\n):\n    url = \"https://raw.githubusercontent.com/trentmc/branin/main/branin.arff\"\n    files = [UrlFile(url)] if not more_files else [UrlFile(url), get_file2()]\n\n    if not metadata:\n        metadata = get_default_metadata()\n\n    data_nft, dts, ddo = ocean_instance.assets.create(\n        metadata,\n        {\"from\": publisher_wallet},\n        datatoken_args=[DatatokenArguments(\"Branin: DT1\", \"DT1\", files=files)],\n    )\n\n    return data_nft, dts[0], ddo\n\n\ndef get_registered_asset_with_compute_service(\n    ocean_instance: Ocean,\n    publisher_wallet,\n    allow_raw_algorithms: bool = False,\n    trusted_algorithms: List[DDO] = [],\n    trusted_algorithm_publishers: List[str] = [],\n):\n    # Set the compute values for compute service\n    compute_values = {\n        \"allowRawAlgorithm\": allow_raw_algorithms,\n        \"allowNetworkAccess\": True,\n        \"publisherTrustedAlgorithms\": trusted_algorithms,\n        \"publisherTrustedAlgorithmPublishers\": trusted_algorithm_publishers,\n    }\n\n    return ocean_instance.assets.create_url_asset(\n        \"Branin\",\n        \"https://raw.githubusercontent.com/oceanprotocol/c2d-examples/main/branin_and_gpr/branin.arff\",\n        tx_dict={\"from\": publisher_wallet},\n        compute_values=compute_values,\n        wait_for_aqua=False,\n    )\n\n\ndef get_first_service_by_type(ddo, service_type: str) -> Service:\n    \"\"\"Return the first Service with the given service type.\"\"\"\n    return next((service for service in ddo.services if service.type == service_type))\n"
  },
  {
    "path": "tests/resources/helper_functions.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport json\nimport logging\nimport logging.config\nimport os\nimport secrets\nfrom datetime import datetime, timezone\nfrom decimal import Decimal\nfrom typing import Any, Dict, Optional, Tuple, Union\n\nimport coloredlogs\nfrom enforce_typing import enforce_types\nfrom eth_account import Account\nfrom web3 import Web3\n\nfrom ocean_lib.example_config import get_config_dict\nfrom ocean_lib.models.data_nft import DataNFT\nfrom ocean_lib.models.data_nft_factory import DataNFTFactoryContract\nfrom ocean_lib.models.datatoken_base import DatatokenBase\nfrom ocean_lib.ocean.ocean import Ocean\nfrom ocean_lib.ocean.util import get_address_of_type, send_ether, to_wei\nfrom ocean_lib.structures.file_objects import FilesTypeFactory\nfrom ocean_lib.web3_internal.constants import ZERO_ADDRESS\nfrom ocean_lib.web3_internal.utils import sign_with_key, split_signature\nfrom tests.resources.mocks.data_provider_mock import DataProviderMock\n\n_NETWORK = \"ganache\"\n\n\n@enforce_types\ndef get_wallet(index: int):\n    return Account.from_key(private_key=os.getenv(f\"TEST_PRIVATE_KEY{index}\"))\n\n\n@enforce_types\ndef get_publisher_wallet():\n    return get_wallet(1)\n\n\n@enforce_types\ndef get_consumer_wallet():\n    return get_wallet(2)\n\n\n@enforce_types\ndef get_another_consumer_wallet():\n    return get_wallet(3)\n\n\n@enforce_types\ndef get_provider_wallet():\n    return Account.from_key(private_key=os.getenv(\"PROVIDER_PRIVATE_KEY\"))\n\n\ndef get_factory_deployer_wallet(config):\n    if config[\"NETWORK_NAME\"] == \"development\":\n        return get_ganache_wallet()\n\n    private_key = os.environ.get(\"FACTORY_DEPLOYER_PRIVATE_KEY\")\n    if not private_key:\n        return None\n\n    config = get_config_dict()\n    return Account.from_key(private_key=private_key)\n\n\ndef get_ganache_wallet():\n    return Account.from_key(\n        private_key=\"0xc594c6e5def4bab63ac29eed19a134c130388f74f019bc74b8f4389df2837a58\"\n    )\n\n\n@enforce_types\ndef generate_wallet():\n    \"\"\"Generates wallets on the fly with funds.\"\"\"\n    config = get_config_dict()\n    secret = secrets.token_hex(32)\n    private_key = \"0x\" + secret\n\n    new_wallet = Account.from_key(private_key=private_key)\n    deployer_wallet = get_factory_deployer_wallet(config)\n    send_ether(\n        config,\n        deployer_wallet,\n        new_wallet.address,\n        to_wei(3),\n    )\n\n    ocean = Ocean(config)\n    OCEAN = ocean.OCEAN_token\n    OCEAN.transfer(new_wallet, to_wei(50), {\"from\": deployer_wallet})\n    return new_wallet\n\n\ndef get_ocean_instance_prerequisites(use_provider_mock=False) -> Ocean:\n    config_dict = get_config_dict()\n    data_provider = DataProviderMock if use_provider_mock else None\n    return Ocean(config_dict, data_provider=data_provider)\n\n\n@enforce_types\ndef get_publisher_ocean_instance(use_provider_mock=False) -> Ocean:\n    ocn = get_ocean_instance_prerequisites(use_provider_mock)\n    ocn.main_account = get_publisher_wallet()\n    return ocn\n\n\n@enforce_types\ndef get_consumer_ocean_instance(use_provider_mock: bool = False) -> Ocean:\n    ocn = get_ocean_instance_prerequisites(use_provider_mock)\n    ocn.main_account = get_consumer_wallet()\n    return ocn\n\n\n@enforce_types\ndef get_another_consumer_ocean_instance(use_provider_mock: bool = False) -> Ocean:\n    ocn = get_ocean_instance_prerequisites(use_provider_mock)\n    ocn.main_account = get_another_consumer_wallet()\n    return ocn\n\n\n@enforce_types\ndef setup_logging(\n    default_level=logging.INFO,\n):\n    \"\"\"Logging setup.\"\"\"\n    logging.basicConfig(level=default_level)\n    coloredlogs.install(level=default_level)\n\n\n@enforce_types\ndef deploy_erc721_erc20(\n    config_dict: dict,\n    data_nft_publisher,\n    datatoken_minter: Optional = None,\n    template_index: Optional[int] = 1,\n) -> Union[DataNFT, Tuple[DataNFT, DatatokenBase]]:\n    \"\"\"Helper function to deploy an DataNFT using data_nft_publisher Wallet\n    and an Datatoken data token with the newly DataNFT using datatoken_minter Wallet\n    if the wallet is provided.\n    :rtype: Union[DataNFT, Tuple[DataNFT, DatatokenBase]]\n    \"\"\"\n\n    data_nft_factory = DataNFTFactoryContract(\n        config_dict, get_address_of_type(config_dict, \"ERC721Factory\")\n    )\n    data_nft = data_nft_factory.create({\"from\": data_nft_publisher}, \"NFT\", \"NFTSYMBOL\")\n\n    if not datatoken_minter:\n        return data_nft\n\n    datatoken_cap = to_wei(100) if template_index == 2 else None\n\n    datatoken = data_nft.create_datatoken(\n        {\"from\": data_nft_publisher},\n        template_index=template_index,\n        cap=datatoken_cap,\n        name=\"DT1\",\n        symbol=\"DT1Symbol\",\n        minter=datatoken_minter.address,\n    )\n\n    return data_nft, datatoken\n\n\n@enforce_types\ndef get_non_existent_nft_template(\n    data_nft_factory: DataNFTFactoryContract, check_first=20\n) -> int:\n    \"\"\"Helper function to find a non existent ERC721 template among the first *check_first* templates\n    of an Data NFT Factory contract. Returns -1 if template was found.\n    \"\"\"\n    for template_nbr in range(check_first):\n        [address, _] = data_nft_factory.getNFTTemplate(template_nbr)\n        if address == ZERO_ADDRESS:\n            return template_nbr\n\n    return -1\n\n\n@enforce_types\ndef send_mock_usdc_to_address(config: dict, recipient: str, amount: int) -> int:\n    \"\"\"Helper function to send mock usdc to an arbitrary recipient address if factory_deployer has enough balance\n    to send. Returns the transferred balance.\n    \"\"\"\n    factory_deployer = get_factory_deployer_wallet(config)\n\n    mock_usdc = DatatokenBase.get_typed(config, get_address_of_type(config, \"MockUSDC\"))\n    initial_recipient_balance = mock_usdc.balanceOf(recipient)\n\n    if mock_usdc.balanceOf(factory_deployer) >= amount:\n        mock_usdc.transfer(recipient, amount, factory_deployer)\n\n    return mock_usdc.balanceOf(recipient) - initial_recipient_balance\n\n\n@enforce_types\ndef transfer_bt_if_balance_lte(\n    config: dict,\n    bt_address: str,\n    from_wallet,\n    recipient: str,\n    min_balance: int,\n    amount_to_transfer: int,\n) -> int:\n    \"\"\"Helper function to send an arbitrary amount of ocean to recipient address if recipient's ocean balance\n    is less or equal to min_balance and from_wallet has enough ocean balance to send.\n    Returns the transferred ocean amount.\n    \"\"\"\n    base_token = DatatokenBase.get_typed(config, bt_address)\n    initial_recipient_balance = base_token.balanceOf(recipient)\n    if (\n        initial_recipient_balance <= min_balance\n        and base_token.balanceOf(from_wallet) >= amount_to_transfer\n    ):\n        base_token.transfer(recipient, amount_to_transfer, {\"from\": from_wallet})\n\n    return base_token.balanceOf(recipient) - initial_recipient_balance\n\n\n@enforce_types\ndef get_provider_fees(\n    provider_wallet,\n    provider_fee_token: str,\n    provider_fee_amount: int,\n    valid_until: int,\n    compute_env: str = None,\n    timestamp: int = None,\n) -> Dict[str, Any]:\n    \"\"\"Copied and adapted from\n    https://github.com/oceanprotocol/provider/blob/b9eb303c3470817d11b3bba01a49f220953ed963/ocean_provider/utils/provider_fees.py#L22-L74\n\n    Keep this in sync with the corresponding provider fee logic when it changes!\n    \"\"\"\n    provider_fee_address = provider_wallet.address\n\n    provider_data = json.dumps(\n        {\n            \"environment\": compute_env,\n            \"timestamp\": datetime.now(timezone.utc).timestamp(),\n        },\n        separators=(\",\", \":\"),\n    )\n    message_hash = Web3.solidity_keccak(\n        [\"bytes\", \"address\", \"address\", \"uint256\", \"uint256\"],\n        [\n            Web3.to_hex(Web3.to_bytes(text=provider_data)),\n            provider_fee_address,\n            provider_fee_token,\n            provider_fee_amount,\n            valid_until,\n        ],\n    )\n\n    signed = sign_with_key(message_hash, os.getenv(\"PROVIDER_PRIVATE_KEY\"))\n\n    provider_fee = {\n        \"providerFeeAddress\": provider_fee_address,\n        \"providerFeeToken\": provider_fee_token,\n        \"providerFeeAmount\": str(provider_fee_amount),\n        \"providerData\": Web3.to_hex(Web3.to_bytes(text=provider_data)),\n        # make it compatible with last openzepellin https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1622\n        \"v\": (signed.v + 27) if signed.v <= 1 else signed.v,\n        \"r\": Web3.to_hex(Web3.to_bytes(signed.r).rjust(32, b\"\\0\")),\n        \"s\": Web3.to_hex(Web3.to_bytes(signed.s).rjust(32, b\"\\0\")),\n        \"validUntil\": valid_until,\n    }\n    return provider_fee\n\n\ndef convert_bt_amt_to_dt(\n    bt_amount: int,\n    bt_decimals: int,\n    dt_per_bt_in_wei: int,\n) -> int:\n    \"\"\"Convert base tokens to equivalent datatokens, accounting for differences\n    in decimals and exchange rate.\n    dt_per_bt_in_wei = 1 / bt_per_dt = 1 / price\n    Datatokens always have 18 decimals, even if base tokens don't.\n    \"\"\"\n    bt_amount_wei = bt_amount\n\n    bt_amount_float = float(bt_amount_wei) / 10**bt_decimals\n\n    dt_per_bt_float = float(dt_per_bt_in_wei) / 10**18  # price always has 18 dec\n\n    dt_amount_float = bt_amount_float * dt_per_bt_float\n\n    dt_amount_wei = int(dt_amount_float * 10**18)\n\n    return dt_amount_wei\n\n\ndef get_file1():\n    file1_dict = {\n        \"type\": \"url\",\n        \"url\": \"https://raw.githubusercontent.com/tbertinmahieux/MSongsDB/master/Tasks_Demos/CoverSongs/shs_dataset_test.txt\",\n        \"method\": \"GET\",\n    }\n\n    return FilesTypeFactory(file1_dict)\n\n\ndef get_file2():\n    file2_dict = {\n        \"type\": \"url\",\n        \"url\": \"https://dumps.wikimedia.org/enwiki/latest/enwiki-latest-abstract10.xml.gz-rss.xml\",\n        \"method\": \"GET\",\n    }\n\n    return FilesTypeFactory(file2_dict)\n\n\ndef get_file3():\n    file3_dict = {\n        \"type\": \"url\",\n        \"url\": \"https://dumps.wikimedia.org/enwiki/latest/enwiki-latest-abstract10.xml.gz\",\n        \"method\": \"GET\",\n    }\n\n    return FilesTypeFactory(file3_dict)\n\n\ndef int_units(amount, num_decimals):\n    decimal_amount = Decimal(amount)\n    unit_value = Decimal(10) ** num_decimals\n\n    return int(decimal_amount * unit_value)\n\n\n@enforce_types\ndef get_mock_provider_fees(mock_type, wallet, valid_until=0):\n    config = get_config_dict()\n    provider_fee_address = wallet.address\n    provider_fee_token = get_address_of_type(config, mock_type)\n    provider_fee_amount = 0\n    provider_data = json.dumps({\"timeout\": 0}, separators=(\",\", \":\"))\n\n    message = Web3.solidity_keccak(\n        [\"bytes\", \"address\", \"address\", \"uint256\", \"uint256\"],\n        [\n            Web3.to_hex(Web3.to_bytes(text=provider_data)),\n            wallet.address,\n            provider_fee_token,\n            provider_fee_amount,\n            valid_until,\n        ],\n    )\n\n    signed = config[\"web3_instance\"].eth.sign(wallet.address, data=message)\n    signature = split_signature(signed)\n\n    return {\n        \"providerFeeAddress\": provider_fee_address,\n        \"providerFeeToken\": provider_fee_token,\n        \"providerFeeAmount\": provider_fee_amount,\n        \"v\": signature.v,\n        \"r\": signature.r,\n        \"s\": signature.s,\n        \"validUntil\": valid_until,\n        \"providerData\": Web3.to_hex(Web3.to_bytes(text=provider_data)),\n    }\n"
  },
  {
    "path": "tests/resources/keys/key_file_1.json",
    "content": "{\n  \"id\": \"50aa801a-8d66-1402-1fa4-d8987868c2ce\",\n  \"version\": 3,\n  \"crypto\": {\n    \"cipher\": \"aes-128-ctr\",\n    \"cipherparams\": {\n      \"iv\": \"a874e6fe50a5bb088826c45560dc1b7e\"\n    },\n    \"ciphertext\": \"2383c6aa50c744b6558e77b5dcec6137f647c81f10f71f22a87321fd1306056c\",\n    \"kdf\": \"pbkdf2\",\n    \"kdfparams\": {\n      \"c\": 10240,\n      \"dklen\": 32,\n      \"prf\": \"hmac-sha256\",\n      \"salt\": \"eca6ccc9fbb0bdc3a516c7576808ba5031669e6878f3bb95624ddb46449e119c\"\n    },\n    \"mac\": \"14e9a33a45ae32f88a0bd5aac14521c1fcf14f56fd55c1a1c080b2f81ddb8d44\"\n  },\n  \"address\": \"068ed00cf0441e4829d9784fcbe7b9e26d4bd8d0\",\n  \"name\": \"\",\n  \"meta\": \"{}\"\n}\n"
  },
  {
    "path": "tests/resources/keys/key_file_2.json",
    "content": "{\n  \"id\": \"0902d04b-f26e-5c1f-e3ae-78d2c1cb16e7\",\n  \"version\": 3,\n  \"crypto\": {\n    \"cipher\": \"aes-128-ctr\",\n    \"cipherparams\": {\n      \"iv\": \"6a829fe7bc656d85f6c2e9fd21784952\"\n    },\n    \"ciphertext\": \"1bfec0b054a648af8fdd0e85662206c65a4af0ed15fede4ad41ca9ab7b504ce2\",\n    \"kdf\": \"pbkdf2\",\n    \"kdfparams\": {\n      \"c\": 10240,\n      \"dklen\": 32,\n      \"prf\": \"hmac-sha256\",\n      \"salt\": \"95f96b5ee22dd537e06076eb8d7078eb7275d29af935782fe476696b11be50e5\"\n    },\n    \"mac\": \"4af2215c3cd9447a5b0512d7d1c3ea5a4435981e1c8f48bf71d7a49c0e5b4986\"\n  },\n  \"address\": \"00bd138abd70e2f00903268f3db08f2d25677c9e\",\n  \"name\": \"Validator0\",\n  \"meta\": \"{}\"\n}\n"
  },
  {
    "path": "tests/resources/mocks/__init__.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n"
  },
  {
    "path": "tests/resources/mocks/data_provider_mock.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\n\nimport os\n\nfrom ocean_lib.data_provider.data_service_provider import DataServiceProvider\n\n\nclass DataProviderMock(DataServiceProvider):\n    def __init__(self, ocean_instance=None, wallet=None):\n        \"\"\"Initialises DataProviderMock object.\"\"\"\n        if not ocean_instance:\n            from tests.resources.helper_functions import get_publisher_ocean_instance\n\n            ocean_instance = get_publisher_ocean_instance(use_provider_mock=True)\n\n        self.ocean_instance = ocean_instance\n        self.wallet = wallet\n        if not wallet:\n            from tests.resources.helper_functions import get_publisher_wallet\n\n            self.wallet = get_publisher_wallet()\n\n    @staticmethod\n    def consume_service(\n        did, service_endpoint, wallet_address, files, destination_folder, *_, **__\n    ):\n        for f in files:\n            with open(\n                os.path.join(destination_folder, os.path.basename(f[\"url\"])), \"w\"\n            ) as of:\n                of.write(f\"mock data {did}.{service_endpoint}.{wallet_address}\")\n\n    @staticmethod\n    def start_compute_job(*args, **kwargs):\n        return True\n\n    @staticmethod\n    def stop_compute_job(*args, **kwargs):\n        return True\n\n    @staticmethod\n    def delete_compute_job(*args, **kwargs):\n        return True\n\n    @staticmethod\n    def compute_job_status(*args, **kwargs):\n        return True\n\n    @staticmethod\n    def compute_job_result(*args, **kwargs):\n        return True\n\n    @staticmethod\n    def compute_job_result_file(*args, **kwargs):\n        return True\n\n    @staticmethod\n    def get_url(config):\n        return DataServiceProvider.get_url(config)\n"
  },
  {
    "path": "tests/resources/mocks/http_client_mock.py",
    "content": "#\n# Copyright 2023 Ocean Protocol Foundation\n# SPDX-License-Identifier: Apache-2.0\n#\nimport inspect\nimport json\nfrom unittest.mock import Mock\n\nfrom requests.models import Response\nfrom requests.sessions import Session\n\nTEST_SERVICE_ENDPOINTS = {\n    \"computeDelete\": [\"DELETE\", \"/api/services/compute\"],\n    \"computeStart\": [\"POST\", \"/api/services/compute\"],\n    \"computeStatus\": [\"GET\", \"/api/services/compute\"],\n    \"computeStop\": [\"PUT\", \"/api/services/compute\"],\n    \"computeResult\": [\"GET\", \"/api/services/computeResult\"],\n    \"download\": [\"GET\", \"/api/services/download\"],\n    \"encrypt\": [\"POST\", \"/api/services/encrypt\"],\n    \"decrypt\": [\"POST\", \"/api/services/decrypt\"],\n    \"fileinfo\": [\"POST\", \"/api/services/fileinfo\"],\n    \"initialize\": [\"GET\", \"/api/services/initialize\"],\n    \"initializeCompute\": [\"POST\", \"/api/services/initializeCompute\"],\n    \"nonce\": [\"GET\", \"/api/services/nonce\"],\n    \"computeEnvironments\": [\"GET\", \"/api/services/computeEnvironments\"],\n    \"create_auth_token\": [\"GET\", \"/api/services/createAuthToken\"],\n    \"delete_auth_token\": [\"DELETE\", \"/api/services/deleteAuthToken\"],\n    \"validateContainer\": [\"POST\", \"/api/services/validateContainer\"],\n}\n\n\nclass HttpClientMockBase(Session):\n    \"\"\"Parent class for all HTTPClient mocks.\"\"\"\n\n    @classmethod\n    def get(cls, *args, **kwargs):\n        \"\"\"Handles the base case of service endpoints.\"\"\"\n        is_get_endpoints_request = False\n        for _, _, _, fn, _, _ in inspect.getouterframes(inspect.currentframe()):\n            if fn == \"get_service_endpoints\":\n                is_get_endpoints_request = True\n\n        if is_get_endpoints_request:\n            the_response = Mock(spec=Response)\n            the_response.status_code = 200\n            the_response.json.return_value = {\n                \"serviceEndpoints\": TEST_SERVICE_ENDPOINTS\n            }\n            return the_response\n\n        return cls.specific_get(*args, **kwargs)\n\n\nclass HttpClientEvilMock(HttpClientMockBase):\n    \"\"\"Mock that generally returns 400 results and errors.\"\"\"\n\n    @staticmethod\n    def post(*args, **kwargs):\n        the_response = Mock(spec=Response)\n        the_response.status_code = 400\n        the_response.text = \"Bad request (mocked).\"\n\n        return the_response\n\n    @staticmethod\n    def specific_get(*args, **kwargs):\n        the_response = Mock(spec=Response)\n        the_response.status_code = 400\n        the_response.text = \"Bad request (mocked).\"\n\n        return the_response\n\n\nclass HttpClientEmptyMock(HttpClientMockBase):\n    \"\"\"Mock unresponsiveness.\"\"\"\n\n    @staticmethod\n    def post(*args, **kwargs):\n        return None\n\n    @staticmethod\n    def specific_get(*args, **kwargs):\n        return None\n\n\nclass HttpClientNiceMock(HttpClientMockBase):\n    \"\"\"Mock that returns 200 results and successful responses.\"\"\"\n\n    @staticmethod\n    def specific_get(*args, **kwargs):\n        the_response = Mock(spec=Response)\n        the_response.status_code = 200\n        the_response.content = '{\"good_job\": \"with_mock\"}'.encode(\"utf-8\")\n\n        return the_response\n\n    @staticmethod\n    def return_nice_response(indication, *args, **kwargs):\n        the_response = Mock(spec=Response)\n        the_response.status_code = 200\n        json_result = {\"good_job\": (\"with_mock_\" + indication)}\n        the_response.content = json.dumps(json_result).encode(\"utf-8\")\n\n        return the_response\n\n    @staticmethod\n    def delete(*args, **kwargs):\n        return HttpClientNiceMock.return_nice_response(\"delete\", *args, **kwargs)\n\n    @staticmethod\n    def put(*args, **kwargs):\n        return HttpClientNiceMock.return_nice_response(\"put\", *args, **kwargs)\n\n    @staticmethod\n    def post(*args, **kwargs):\n        return HttpClientNiceMock.return_nice_response(\"post\", *args, **kwargs)\n"
  },
  {
    "path": "tests/resources/test/test_helper_functions.py",
    "content": "from tests.resources.helper_functions import convert_bt_amt_to_dt\n\n\ndef test_convert_bt_amt_to_dt():\n    bt_decimals = 16\n    bt_amt_float = 10.0\n    price_float = 2.0  # price\n\n    expected_dt_amt_float = bt_amt_float / price_float\n\n    bt_amt_wei = int(bt_amt_float * 10**bt_decimals)\n    bt_per_dt_float = 1.0 / price_float\n    dt_per_bt_wei = int(bt_per_dt_float * 10**18)\n\n    dt_amt_wei = convert_bt_amt_to_dt(bt_amt_wei, bt_decimals, dt_per_bt_wei)\n    dt_amt_float = float(dt_amt_wei / 10**18)\n\n    assert dt_amt_float == expected_dt_amt_float\n"
  }
]